<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
 * MaxSite CMS
 * (c) http://max-3000.com/
 * Основные функции
 */

# подключаем библиотеку mbstring
# какие функции отсутствуют определяется в этом файле

global $mso_install;
if ($mso_install and !function_exists('mb_strlen') ) require('mbstring.php');


define("NR", "\n");


# получение нужного значения
function getinfo($info = '')
{
	global $MSO;

	$out = '';

	switch ($info) :
		case 'version' :
				$out = $MSO->version;
				break;

		case 'site_url' :
		case 'siteurl' :
				$out = $MSO->config['site_url'];
				break;

		case 'stylesheet_url' :
		case 'template_url': 
				$out = $MSO->config['templates_url']
						. $MSO->config['template']
						. '/';
				break;
				
		case 'template' :
				$out = $MSO->config['template'];
				break;

		case 'template_dir' :
				$out = $MSO->config['templates_dir'] . $MSO->config['template'] . '/';
				break;

		case 'templates_dir' :
				$out = $MSO->config['templates_dir'];
				break;

		case 'url_new_comment' :
				$out = $MSO->config['site_url'] . 'newcomment';
				break;

		case 'pingback_url' :

				break;

		case 'rss_url' :
				$out = $MSO->config['site_url'] . 'feed';
				break;
		
		case 'rss_comments_url' :
				$out = $MSO->config['site_url'] . 'comments/feed';
				break;
				
		case 'feed' :
				$out = $MSO->config['site_url'] . 'feed';
				break;

		case 'atom_url' :

				break;

		case 'comments_rss2_url' :
				$out = $MSO->config['site_url'] . 'comments/feed';
				break;

		case 'admin_url' :
				$out = $MSO->config['admin_url']; // [admin_url] => http://localhost/application/maxsite/admin/
				break;

		case 'site_admin_url' :
				$out = $MSO->config['site_admin_url']; // [site_admin_url] => http://localhost/admin/
				break;

		case 'common_dir' :
				$out = $MSO->config['common_dir'];
				break;

		case 'common_url' :
				$out = $MSO->config['common_url'];
				break;

		case 'uploads_url' :
				$out = $MSO->config['uploads_url'];
				break;

		case 'uploads_dir' :
				$out = $MSO->config['uploads_dir'];
				break;

		case 'users_nik' :
				if (isset($MSO->data['session']['users_nik']))
					$out = $MSO->data['session']['users_nik'];
				else $out = '';
				break;

		case 'users_id' :
				if (isset($MSO->data['session']['users_id']))
					$out = $MSO->data['session']['users_id'];
				else $out = '';
				break;

		case 'name_site' :
				$out = htmlspecialchars(mso_get_option('name_site', 'general'));
				break;

		case 'description_site' :
				$out = htmlspecialchars(mso_get_option('description_site', 'general'));
				break;

		case 'title' :
				//$out = htmlspecialchars(mso_get_option('title', 'general'));
				$out = mso_get_option('title', 'general');
				break;

		case 'description' :
				$out = htmlspecialchars(mso_get_option('description', 'general'));
				break;

		case 'keywords' :
				$out = htmlspecialchars(mso_get_option('keywords', 'general'));
				break;

		case 'time_zone' :
				$out = (string) mso_get_option('time_zone', 'general');
				break;

		case 'plugins_url' :
				$out = $MSO->config['plugins_url'];;
				break;

		case 'plugins_dir' :
				$out = $MSO->config['plugins_dir'];;
				break;

		case 'ajax' :
				$out = $MSO->config['site_url'] . 'ajax/';
				break;
				
		case 'require-maxsite' :
				$out = $MSO->config['site_url'] . 'require-maxsite/';
				break;
				
		case 'admin_plugins_dir' :
				$out = $MSO->config['admin_plugins_dir'];
				break;

		case 'session' :
				$out = $MSO->data['session'];
				break;

		case 'remote_key' :
				$out = $MSO->config['remote_key'];
				break;

		case 'uri_get' :
				$out = $MSO->data['uri_get'];
				break;
				
		case 'admin_dir' :
				$out = $MSO->config['admin_dir'];
				break;
				
		case 'cache_dir' :
				$out = $MSO->config['cache_dir'];
				break;

		case 'FCPATH' :
				$out = $MSO->config['FCPATH'];
				break;
				
		case 'type' :
				$out = $MSO->data['type'];
				break;
				
		case 'type_foreach_file':
				$out = isset($MSO->data['type_foreach_file']) ? $MSO->data['type_foreach_file'] : '';
				break;

	endswitch;

	return $out;
}


#  функция для отладки
function pr($var, $html = false, $echo = true)
{
	if (!$echo) ob_start();
		else echo '<pre>';
	if (is_bool($var))
	{
		if ($var) echo 'TRUE';
			else echo 'FALSE';
	}
	else
	{
		if ( is_scalar($var) )
		{
			if (!$html) echo $var;
				else
				{
					$var = str_replace('<br />', "<br>", $var);
					$var = str_replace('<br>', "<br>\n", $var);
					$var = str_replace('</p>', "</p>\n", $var);
					$var = str_replace('<ul>', "\n<ul>", $var);
					$var = str_replace('<li>', "\n<li>", $var);
					$var = htmlspecialchars($var);
					$var = wordwrap($var, 300);
					echo $var;
				}
		}
			else print_r ($var);
	}
	if (!$echo)
	{
		$out = ob_get_contents();
		ob_end_clean();
		return $out;
	}
	else echo '</pre>';
}


# функция, аналогичная pr, только завершающаяся die() 
# используется для отладки с помощью прерывания
function _pr($var, $html = false, $echo = true)
{
	pr($var, $html, $echo);
	die();
}


# функция, формирующая sql-запрос
# используется для отладки перед $CI->db->get()
function _sql()
{
	$CI = & get_instance();
	$sql = $CI->db->_compile_select();
	return $sql;
}


#  правильность email
function mso_valid_email($address = '')
{
	# из helpers/email_helper.php
	return ( ! preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $address)) ? FALSE : TRUE;
}


# проверем рефер на xss-атаку
# работает только если есть POST
function mso_checkreferer()
{
	if ($_POST)
	{
		if (!isset($_SERVER['HTTP_REFERER'])) die('<b><font color="red">Achtung! XSS attack! No REFERER!</font></b>');

		$ps = parse_url($_SERVER['HTTP_REFERER']);
		
		if (isset($ps['host'])) $p = $ps['host'];
			else $p = '';
		
		if ($p and isset($ps['port']) and $ps['port'] != 80) $p .= ':' . $ps['port'];
		
		if ($p != $_SERVER['HTTP_HOST'])
			die('<b><font color="red">Achtung! XSS attack!</font></b>');
	}
}


# защита сессии
# сравниваем переданную сессию с текущей
# и если указан редирект, то в случае несовпадения переходим по нему
# иначе возвращаем true - если все ок и false - ошибка сессии
function mso_checksession($session_id, $redirect = false)
{
	global $MSO;

	$result = ($MSO->data['session']['session_id'] == $session_id );

	if ($redirect and !$result)
	{
		mso_redirect($redirect);
		return;
	}

	return $result;
}


# удаляем все лишнее в формах
# если второй параметр = true то возвращает false, если данные после стрипа изменились и $s - теже
function mso_strip($s = '', $logical = false, $arr_strip = array('\\', '|', '/', '?', '%', '*', '`'))
{
	$s1 = $s;
	$s1 = stripslashes($s1);
	$s1 = strip_tags($s1);
	$s1 = htmlspecialchars($s1, ENT_QUOTES);

	$s1 = str_replace($arr_strip, '', $s1);
	$s1 = trim($s1);

	if ($logical)
	{
		if ($s1 === $s) return $s;
			else return false;
	}
	else
		return $s1;
}


# функция инициализации
function mso_initalizing()
{
	global $MSO, $mso_install;
	$CI = & get_instance();

	# считываем файл конфигурации
	$fn = $MSO->config['config_file'];
	if ( file_exists($fn) ) require_once ($fn);

	// если кэш старый, то очищаем его
	// #ci1 $path = $CI->config->item('cache_path');
	
	$path = getinfo('cache_dir');
	
	
	// #ci1 $mso_cache_last = ($path == '') ? BASEPATH . 'cache/' . '_mso_cache_last.txt' : $path . '_mso_cache_last.txt';
	
	$mso_cache_last = $path . '_mso_cache_last.txt';
	
	if (file_exists($mso_cache_last))
	{
		$time = (int) trim(implode('', file($mso_cache_last)));
		$time = $time + $MSO->config['cache_time'] + 60; // запас + 60 секунд
		if (time() > $time) mso_flush_cache(); // время истекло - сбрасываем кэш
	}
	else // файла нет > _mso_cache_last.txt < создадим - наверное совсем старый кэш
	{
		mso_flush_cache();
	}

	# стоит ли флаг, что уже произведена инсталяция?

	if (!isset($mso_install) or $mso_install == false)
	{
		if ( !$CI->db->table_exists('options')) return false; # еще не установлен сайт
	}

	# подключаем опции - они могут быть в кэше
	global $cache_options;

	if ( $opt = mso_get_cache('options') ) # есть кэш опций
		$cache_options = $opt;
	else
		mso_refresh_options(); # обновляем кэш опций


	# проверим текущий шаблон
	$template = mso_get_option('template', 'general'); // считали из опций
	$index = $MSO->config['templates_dir'] . $template . '/index.php'; // проверим в реале

	if (!file_exists($index)) // нет такого шаблона - меняем на дефолтный
	{
		mso_add_option('template', 'default', 'general');
		$MSO->config['template'] = 'default';
	}
	else // все ок
		$MSO->config['template'] = $template;

	# подключим файл functions.php в шаблоне - если есть
	$functions_file = $MSO->config['templates_dir'] . $MSO->config['template'] . '/functions.php';
	if (file_exists($functions_file))
	{
		require_once($functions_file);
	}

	# проверяем залогинненость юзера
	if (!isset($CI->session->userdata['userlogged']) or !$CI->session->userdata['userlogged'] )
	{
		// не залогинен
		$CI->session->userdata['userlogged'] = 0;
	}
	else
	{
		// отмечено, что залогинен
		// нужно проверить верность данных юзера
		$CI->db->from('users'); # таблица users
		$CI->db->select('users_id, users_groups_id');
		$CI->db->limit(1); # одно значение

		$CI->db->where( array('users_login'=>$CI->session->userdata['users_login'],
							  'users_password'=>$CI->session->userdata['users_password']) );

		$query = $CI->db->get();

		if (!$query or $query->num_rows() == 0) # нет такого - возможно взлом
		{
			$CI->session->sess_destroy(); // убиваем сессию
			$CI->session->userdata['userlogged'] = 0; // отмечаем, что не залогинен
		}
		else
		{
			// есть что-то
			$row = $query->row();
			// сразу выставим группу
			$MSO->data['session']['users_groups_id'] = $row->users_groups_id;

			# сразу обновляем время последней активности сессии
			$CI->session->set_userdata('last_activity', time());
		}
	}

	// аналогично проверяем и комюзера, только данные из куки
	// но при этом сразу сохраняем все данные комюзера, чтобы потом не обращаться к БД

	$comuser = mso_get_cookie('maxsite_comuser', false);
	if ($comuser)
	{
		$comuser = unserialize($comuser);
		/*
		[comusers_id] => 1
		[comusers_password] => 037035235237852
		[comusers_email] => max-3000@list.ru
		[comusers_nik] => Максим
		[comusers_url] => http://maxsite.org/
		[comusers_avatar_url] => http://maxsite.org/avatar.jpg
		*/
		// нужно сверить с тем, что есть

		$CI->db->select('comusers_id, comusers_password, comusers_email');
		$CI->db->where('comusers_id', $comuser['comusers_id']);
		$CI->db->where('comusers_password', $comuser['comusers_password']);
		$CI->db->where('comusers_email', $comuser['comusers_email']);
		$query = $CI->db->get('comusers');
		if ($query->num_rows()) // есть такой комюзер
		{
			$CI->session->userdata['comuser'] = $comuser;
		}
		else // неверные данные
		{
			$CI->session->userdata['comuser'] = 0;
		}
	}
	else $CI->session->userdata['comuser'] = 0;


	# дефолтные хуки
	mso_hook_add('content_auto_tag', 'mso_auto_tag'); // авторасстановка тэгов
	mso_hook_add('content_balance_tags', 'mso_balance_tags'); // автозакрытие тэгов - их баланс
}


# проверка залогиннености юзера
function is_login()
{
	global $MSO;
	return ($MSO->data['session']['userlogged'] == 1) ? true : false;
}


# проверка залогиннености комюзера
# если есть, то возвращает массив данных
function is_login_comuser()
{
	global $MSO;

	if (isset($MSO->data['session']['comuser']) and ($comuser = $MSO->data['session']['comuser']) ) return $comuser;
		else return false;
}


# загружаем включенные плагины
function mso_autoload_plugins()
{
	global $MSO;

	// функция mso_autoload_custom может быть в mso_config.php
	if ( function_exists('mso_autoload_custom') ) mso_autoload_custom();

	$d = mso_get_option('active_plugins', 'general');
	if (!$d)
	{
		$d = $MSO->active_plugins;
	}

	foreach ($d as $load) mso_plugin_load($load);
}


# проверка типа страницы, который определился в контролере
function is_type($type)
{
	global $MSO;
	return ( $MSO->data['type'] == $type ) ? true : false;
}


# возвращает true или false при проверке $MSO->data['uri_segment'], то есть по сегментам URL
# где например [1] => page  [2] => about
# что означает type = page  slug=about
# http://localhost/page/about
# можно указать только тип или только slug
# тогда неуказанный параметр не учитывается (всегда true)
function is_type_slug($type = '', $slug = '')
{
	global $MSO;

	$rt = $rs = '';

	// тип
	if ($type and isset($MSO->data['uri_segment'][1]) ) $rt = $MSO->data['uri_segment'][1];

	// slug
	if ( $slug and isset($MSO->data['uri_segment'][2]) ) $rs = $MSO->data['uri_segment'][2];

	return ($rt == $type and $rs == $slug);
}


# проверяем рубрику у страницы
# если это page и есть указанная рубрика, то возвращаем true
# если это не page или нет указанной рубрики, то возвращаем false
# если $and_id = true , то ищем и по id
# если $and_name = true , то ищем и по category_name
function is_page_cat($slug = '', $and_id = true, $and_name = true)
{
	global $MSO, $page;

	if (!$slug) return false; // slaug не указан
	if (!is_type('page')) return false; // тип не page
	if (!isset($page['page_categories_detail'])) return false; // нет информации о рубриках

	$result = false;

	// информация о slug, id и name в массиве $page['page_categories_detail']
	foreach($page['page_categories_detail'] as $id => $val)
	{
		if ( $val['category_slug'] == $slug ) $result = true; // slug совпал
		if ( !$result and $and_id and $id == $slug ) $result = true; // можно искать по $id
		if ( !$result and $val['category_name'] == $slug ) $result = true; // category_name совпал

		if ($result) break;
	}
	return $result;
}


# проверка если feed
function is_feed()
{
	global $MSO;
	return $MSO->data['is_feed'] ? true : false;
}


# вывод html meta титла дескриптон или keywords страницы
function mso_head_meta($info = 'title', $args = '', $format = '%page_title%', $sep = '', $only_meta = false )
{
	// ошибочный info
	if ( $info != 'title' and $info != 'description' and $info != 'keywords') return '';


	if (mso_hook_present('head_meta')) // если есть хуки, то управление передаем им
	{
		return mso_hook('head_meta', array('info'=>$info, 'args'=>$args, 'format'=>$format, 'sep'=>$sep, 'only_meta'=>$only_meta));
	}

	global $MSO;

	// измененный для вывода титле хранится в $MSO->title description или keywords

	if (!$args) // нет аргумента - выводим что есть
	{
		if ( !$MSO->$info )	$out = $MSO->$info = getinfo($info);
		else $out = $MSO->$info;
	}
	else // есть аргументы
	{
		if (is_scalar($args)) $out = $args; // какая-то явная строка - отдаем её как есть
		else // входной массив - скорее всего это страница
		{
			// %page_title% %title% %category_name%
			// | это разделитель, который = $sep
			// pr($args);

			$category_name = '';
			$category_desc = '';
			$page_title = '';
			$users_nik = '';
			$title = getinfo($info);

			// if ( !$info ) $format = '%title%';

			// название рубрики
			if ( isset($args[0]['category_name']) ) 
			{
				//$category_name = htmlspecialchars($args[0]['category_name']);
				$category_name = $args[0]['category_name'];
				
				// по названию рубрики ищем её описание в $args[0]['page_categories_detail'][$id]['category_desc']
				if (isset($args[0]['page_categories_detail']))
				{
					foreach ($args[0]['page_categories_detail'] as $id => $val)
					{
						if ($args[0]['category_name'] === $val['category_name'] )
						{
							$category_desc = htmlspecialchars($val['category_desc']);
							break;
						}
					}
				}
				
				if (!$category_desc) $category_desc = $category_name; // если нет описания, то берем название
			}
			
			if ( isset($args[0]['page_title']) ) $page_title = $args[0]['page_title'];
			if ( isset($args[0]['users_nik']) ) $users_nik = $args[0]['users_nik'];

			// если есть мета, то берем её
			if ( isset($args[0]['page_meta'][$info][0]) and $args[0]['page_meta'][$info][0] )
			{
				if ( $only_meta ) $category_name = $category_desc = $title = $sep = '';
				$page_title = $args[0]['page_meta'][$info][0];

				if ( $info!='title') $title = $page_title;
			}

			$arr_key = array( '%title%', '%page_title%',  '%category_name%', '%category_desc%', '%users_nik%', '|' );
			$arr_val = array( htmlspecialchars($title),  htmlspecialchars($page_title), htmlspecialchars($category_name), $category_desc, htmlspecialchars($users_nik), $sep );
			//$arr_val = array( $title ,  $page_title, $category_name, $category_desc, $users_nik, $sep );
			
			$out = str_replace($arr_key, $arr_val, $format);
		}
	}

	// отдаем результат, сразу же указывая измененный $info в $MSO
	$out = $MSO->$info = trim($out);

	return $out;
}


# подключение плагина
function mso_plugin_load($plugin = '')
{
	global $MSO;

	$fn_plugin = $MSO->config['plugins_dir'] . $plugin . '/index.php';

	if ( !file_exists( $fn_plugin ) ) return false;
	else
	{
		require_once ($fn_plugin);

		$auto_load = $plugin . '_autoload';
		if ( function_exists($auto_load) ) $auto_load();

		# добавим плагин в список активных
		$MSO->active_plugins[] = $plugin;
		$MSO->active_plugins = array_unique($MSO->active_plugins);
		sort($MSO->active_plugins);

		return true;
	}
}


# подключение admin-плагина - выполняется только при входе в админку
function mso_admin_plugin_load($plugin = '')
{
	global $MSO;

	$fn_plugin = $MSO->config['admin_plugins_dir'] . $plugin . '/index.php';

	if ( !file_exists( $fn_plugin ) ) return false;
	else
	{
		require_once ($fn_plugin);

		$auto_load = $plugin . '_autoload';
		if ( function_exists($auto_load) ) $auto_load();

		return true;
	}
}


# подключение функции к хуку
# приоритет по-умолчанию 10.
# если нужно, чтобы хук сработал раньше всех, то ставим более 10
# если нужно сработать последним - ставим приоритет менее 10
# http://forum.max-3000.com/viewtopic.php?p=9550#p9550
function mso_hook_add($hook, $func, $priory = 10)
{
	global $MSO;

	$priory = (int) $priory;

	if ( $priory > 0 ) $MSO->hooks[$hook][$func] = $priory;
		else $MSO->hooks[$hook][$func] = 0;

	ksort($MSO->hooks[$hook]);
	arsort($MSO->hooks[$hook]);
}


# прописываем хук к admin_url_+hook
function mso_admin_url_hook($hook, $func, $priory = 0)
{
	// нельзя указывать хуки на зарезервированные адреса: ???
	$hook = strtolower($hook);
	$no_hook = array('');

	if ( !in_array($hook, $no_hook))
		mso_hook_add ('admin_url_' . $hook, $func, $priory = 0);
}


# выполнение хуков
# название хука - переменная для результата
function mso_hook($hook = '', $result = '', $result_if_no_hook = '_mso_result_if_no_hook')
{
	global $MSO;

	if ($hook == '') return $result;

	$arr = array_keys($MSO->hooks);

	if ( !in_array($hook, $arr) ) // если хука нет
	{
		if ($result_if_no_hook != '_mso_result_if_no_hook') // если указана $result_if_no_hook
			return $result_if_no_hook;
		else return $result;
	}

	foreach ( $MSO->hooks[$hook] as $func => $val)
		if ( function_exists($func) ) $result = $func($result);

	return $result;
}


# проверяет существование хука
function mso_hook_present($hook = '')
{
	global $MSO;
	if ($hook == '') return false;
	$arr = array_keys($MSO->hooks);
	if ( !in_array($hook, $arr) ) return false;
		else return true;
}


# удаляет из хука функцию
# если функция не указана, то удаляются все функции из хука
function mso_remove_hook($hook = '', $func = '')
{
	global $MSO;

	if ($hook == '') return false;

	$arr = array_keys($MSO->hooks);
	if ( !in_array($hook, $arr) ) return false; // хука нет

	if ($func == '') // удалить весь хук
	{
		unset($MSO->hooks[$hook]);
	}
	else
	{
		if ( !in_array($hook, $arr) ) return false; // нет такой функции
		unset($MSO->hooks[$hook][$func]);
	}
	return true;
}


# динамическое создание функции на хук
# тело функции дожно работать как нормальный php
# функция принимает только один аргумент $args
function mso_hook_add_dinamic( $hook = '', $func = '', $priory = 10)
{
	if ($hook == '') return false;
	if ($func == '') return false;

	$func_name = @create_function('$args', $func);

	return mso_hook_add( $hook, $func_name, $priory);
}


# генератор md5 свой
function mso_md5($t = '')
{
	global $MSO;

	if ($MSO->config['secret_key'])
		return strrev( md5($t . $MSO->config['secret_key']) );
	else
		return strrev( md5($t . $MSO->config['site_url']) );
}


# сброс кэша опций
function mso_refresh_options()
{
	global $cache_options;

	$CI = & get_instance();

	/*
	$cache_options =
		type = array (
				key  = value
				key1 = value2
				)
	*/
	$CI->db->cache_delete_all();

	$query = $CI->db->get('options');

	$cache_options = array();

	// $query = $ci->db->query('SELECT * FROM ci_options ORDER BY options_type');

	foreach ($query->result() as $row)
		$cache_options[$row->options_type][$row->options_key] = $row->options_value;

	mso_add_cache('options', $cache_options);

	return $cache_options;
}


# добавление в таблицу опций options
function mso_add_option($key, $value, $type = 'general')
{
	# если value массив или объект, то серилизуем его в строку
	if ( !is_scalar($value) ) $value = '_serialize_' . serialize($value);

	$data = array(
			'options_key'=>$key,
			'options_type'=>$type );

	$CI = & get_instance();

	# проверим есть ли уже такой ключ
	$CI->db->select('options_id');
	$CI->db->from('options');
	$CI->db->where($data);

	$query = $CI->db->get();

	if ($query->num_rows() > 0 ) # есть уже такой ключ, поэтому обновляем его значение
	{
		$CI->db->where($data);
		$data['options_value'] = $value;
		$CI->db->update('options', $data);
	}
	else # новый ключ
	{
		$data['options_value'] = $value;
		$CI->db->insert('options', $data);
	}

	mso_refresh_options(); # обновляем опции из базы

	return true;
}


# удаление в таблице опций options ключа с типом
function mso_delete_option($key, $type = 'general')
{
	$CI = & get_instance();

	$CI->db->limit(1);
	$CI->db->delete('options', array('options_key'=>$key, 'options_type'=>$type ));

	mso_refresh_options(); # обновляем опции из базы

	return true;
}


# удаление в таблице опций options ключа-маски с типом
# маска считается от начала, например mask*
function mso_delete_option_mask($mask, $type = 'general')
{
	$CI = & get_instance();

	$mask = str_replace('_', '/_', $mask);
	$mask = str_replace('%', '/%', $mask);

	$query = $CI->db->query('DELETE FROM ' . $CI->db->dbprefix('options') . ' WHERE options_type="' . $type . '" AND options_key LIKE "'. $mask . '%" ESCAPE "/"');

	mso_refresh_options(); # обновляем опции из базы

	return true;
}


# получение опции из кэша опций
function mso_get_option($key, $type = 'general', $return_value = false)
{
	global $cache_options;

	if ( isset($cache_options[$type][$key]) )
		$result = $cache_options[$type][$key];
	else
		$result = $return_value;

	// проверяем на сериализацию
	if (@preg_match( '|_serialize_|A', $result))
	{
		$result = preg_replace( '|_serialize_|A', '', $result, 1 );
		$result = @unserialize($result);
	}

	return $result;
}


# добавление float-опции
# float-опция - это файл из серилизованного текста в каталоге uploads
# аналог опций, хранящейся в отдельном файле/каталоге _mso_float
function mso_add_float_option($key, $value, $type = 'general', $serialize = true, $ext = '', $md5_key = true, $dir = '')
{
	$CI = & get_instance();

	if ($dir) $dir .= '/';

	$path = getinfo('uploads_dir') . '_mso_float/' . $dir;

	if ( ! is_dir($path) ) @mkdir($path, 0777); // нет каталога, пробуем создать

	if ( ! is_dir($path) OR ! is_writable($path)) return false; // нет каталога или он не для записи

	if ($md5_key) $path .= mso_md5($key . $type) . $ext;
		else $path .= $key . $type . $ext;

	if ( ! $fp = @fopen($path, 'wb') ) return false; // нет возможности сохранить файл

	if ($serialize)	$output = serialize($value);
		else $output = $value;

	flock($fp, LOCK_EX);
	fwrite($fp, $output);
	flock($fp, LOCK_UN);
	fclose($fp);
	@chmod($path, 0777);

	// возвращаем имя файла
	if ($md5_key) $return = '_mso_float/' . $dir . mso_md5($key . $type) . $ext;
		else $return = '_mso_float/' . $dir . $key . $type . $ext;

	return $return;
}


# получение данных из flat-опций
function mso_get_float_option($key, $type = 'general', $return_value = false, $serialize = true, $ext = '', $md5_key = true, $dir = '')
{
	$CI = & get_instance();

	if (!$key or !$type) return $return_value;

	if ($dir) $dir .= '/';

	if ($md5_key) $path = getinfo('uploads_dir') . '_mso_float/' . $dir . mso_md5($key . $type) . $ext;
		else $path = getinfo('uploads_dir') . '_mso_float/' . $dir . $key . $type . $ext;

	if ( file_exists($path))
	{
		if ( ! $fp = @fopen($path, 'rb')) return $return_value;

		flock($fp, LOCK_SH);

		$out = $return_value;
		if (filesize($path) > 0)
		{
			if ($serialize) $out = @unserialize(fread($fp, filesize($path)));
				else $out = fread($fp, filesize($path));
		}

		flock($fp, LOCK_UN);
		fclose($fp);

		return $out;
	}
	else return $return_value;
}


# удаление flat-опции если есть
function mso_delete_float_option($key, $type = 'general', $dir = '')
{
	$CI = & get_instance();

	if (!$key or !$type) return false;

	if ($dir) $dir .= '/';

	$path = getinfo('uploads_dir') . '_mso_float/' . $dir . mso_md5($key . $type);

	if ( file_exists($path))
	{
		@unlink($path);
		return true;
	}
	else return false;
}


# добавить кеш
# ключ, значение, время
# Функция взята из _write_cache output.php - немного переделанная
function mso_add_cache($key, $output, $time = false, $custom_fn = false)
{
	# если определен отдельный хук, то выполняем его
	if (mso_hook_present('mso_add_cache')) 
		return mso_hook('mso_add_cache', array(
		'key' => $key,
		'output' => $output,
		'time' => $time,
		'custom_fn' => $custom_fn
		));

		
	global $MSO;

	$CI = & get_instance();
	
	$cache_path = getinfo('cache_dir');

	if ( ! is_dir($cache_path) or ! is_writable($cache_path)) return;

	if (!$custom_fn)
		$cache_path .= mso_md5($key . $CI->config->item('base_url'));
	else
		$cache_path .= $key;

	if ( ! $fp = @fopen($cache_path, 'wb')) return;

	if (!$time) $time = $MSO->config['cache_time'];

	$expire = time() + $time;
	$output = serialize($output);

	flock($fp, LOCK_EX);
	fwrite($fp, $expire.'TS--->' . $output);
	flock($fp, LOCK_UN);
	fclose($fp);
	@chmod($cache_path, 0777);
}


# удаление файла в кэше файлов, начинающихся с указаной строки
function mso_flush_cache_mask($mask = '')
{
	if (!$mask) return;

	# если определен отдельный хук, то выполняем его
	if (mso_hook_present('mso_flush_cache_mask')) 
		return mso_hook('mso_flush_cache_mask', array('mask' => $mask));
	
	$CI = & get_instance();

	$cache_path = getinfo('cache_dir');	

	if ( ! is_dir($cache_path) or ! is_writable($cache_path)) return;

	$CI->load->helper('directory');

	$files = directory_map($cache_path, true); // только в текущем каталоге

	if (!$files) return; // нет файлов вообще

	foreach ($files as $file)
	{
		if (@is_dir($cache_path . $file)) continue; // это каталог

		$pos = strpos($file, $mask);
		if ( $pos !== false and $pos === 0)
		{
			@unlink($cache_path . $file);
		}
	}
}


# сбросить кэш - если указать true, то удалится кэш из вложенных каталогов
# если указан $dir, то удаляется только в этом каталоге
# если указать $file, то удаляется только этот файл в кэше
function mso_flush_cache($full = false, $dir = false, $file = false)
{
	# если определен отдельный хук, то выполняем его
	if (mso_hook_present('mso_flush_cache')) 
		return mso_hook('mso_flush_cache', array(
		'full' => $full,
		'dir' => $dir,
		'file' => $file
		));
		
	$CI = & get_instance();
	
	$cache_path = getinfo('cache_dir');

	if ( ! is_dir($cache_path) OR ! is_writable($cache_path))
		return FALSE;

	// находим в каталоге все файлы и их удалаяем
	if ($full)
	{
		$CI->load->helper('file_helper'); // этот хелпер удаляет все Файлы и во вложенных каталогах
		@delete_files($cache_path);
	}
	else
	{
		// удаляем файлы только в текущем каталоге кэша
		// переделанная функция delete_files из file_helper
		$mso_cache_last = $cache_path . '_mso_cache_last.txt';

		if ($dir) $cache_path .= $dir . '/'; // если указан $dir, удаляем только в нем
		
		if ($file) // указан конкретный файл
		{
			if ( file_exists($cache_path . $file) ) @unlink($cache_path . $file);
		}
		else
		{
			if (!$current_dir = @opendir($cache_path)) return false;
			while (FALSE !== ($filename = @readdir($current_dir)))
			{
				if ($filename != "." and $filename != "..")
				{
					if (!is_dir($cache_path . $filename)) @unlink($cache_path . $filename);
				}
			}
			@closedir($current_dir);
		}

		// создадим служебный файл _mso_cache_last.txt который используется для сброса кэша по дате создания
		// при инициализации смотрится дата этого файла и если он создан позже, чем время жизни кэша, то кэш сбрасывается mso_flush_cache
		if (!$dir)
		{
			$fp = @fopen($mso_cache_last, 'w');
			flock($fp, LOCK_EX);
			fwrite($fp, time());
			flock($fp, LOCK_UN);
			fclose($fp);
		}

	}
	// если используется родное CodeIgniter sql-кэширование, то нужно очистить и его
	$CI->db->cache_delete_all();
}


# получить кеш по ключу
# Функция взята из _display_cache output.php - переделанная
function mso_get_cache($key, $custom_fn = false)
{
	# если определен отдельный хук, то выполняем его
	if (mso_hook_present('mso_get_cache')) 
		return mso_hook('mso_get_cache', array(
		'key' => $key,
		'custom_fn' => $custom_fn
		));

	$CI = & get_instance();
	
	$cache_path = getinfo('cache_dir');
	
	if ( !is_dir($cache_path) OR ! is_writable($cache_path))
		return FALSE;

	if (!$custom_fn)
		$filepath = $cache_path . mso_md5($key . $CI->config->item('base_url'));
	else
		$filepath = $cache_path . $key;

	if ( !@file_exists($filepath))
		return FALSE;

	if ( !$fp = @fopen($filepath, 'rb'))
		return FALSE;

	flock($fp, LOCK_SH);

	$cache = '';
	if (filesize($filepath) > 0)
		$cache = fread($fp, filesize($filepath));

	flock($fp, LOCK_UN);
	fclose($fp);

	if ( ! preg_match("/(\d+TS--->)/", $cache, $match))
		return FALSE;

	if (time() >= trim(str_replace('TS--->', '', $match['1'])))
	{
		@unlink($filepath);
		return FALSE;
	}

	$out = str_replace($match['0'], '', $cache);
	$out = @unserialize($out);

	return $out;
}


# преобразование html-спецсимволов в тексте в обычный html
function mso_text_to_html($content)
{
	//$content = str_replace(chr(10), "\n", $content);
	//$content = str_replace(chr(13), " ", $content);
	//$content = str_replace('&lt;', '<', $content);
	//$content = str_replace('&gt;', '>', $content);
	//$content = str_replace('\"', '&quot;', $content);
	//$content = str_replace('\\', '\\\\',$content);
	//$content = str_replace('\'', '&#039',$content);
	//$content = str_replace('&lt;','<', $content);
	//$content = str_replace('&gt;','>', $content);
	//$content = str_replace('&quot;','\"', $content);
	//$content = str_replace('&#039','\'', $content);
	// $content = str_replace('&amp;','&', $content);

	// $content = htmlspecialchars($content, ENT_QUOTES);
	
	return mso_hook('text_to_html', $content);
}


# преобразование html-спецсимволов
function mso_html_to_text($content)
{
	//$content = str_replace(chr(10), " ", $content);
	//$content = str_replace(chr(13), "", $content);
	//$content = str_replace('&lt;', '<', $content);
	//$content = str_replace('&gt;', '>', $content);
	//$content = str_replace('&amp;','&', $content);

	//$content = str_replace('\"', '&quot;', $content);
	//$content = str_replace('\\', '\\\\',$content);
	//$content = str_replace('\'', '&#039',$content);
	//$content = str_replace('&lt;','<', $content);
	//$content = str_replace('&gt;','>', $content);
	//$content = str_replace('&quot;','\"', $content);
	//$content = str_replace('&#039','\'', $content);

	// $content = htmlspecialchars($content, ENT_QUOTES);

	return mso_hook('html_to_text', $content);
}


# подчистка PRE + mso_auto_tag
function mso_clean_pre_special_chars($matches)
{
	if ( is_array($matches) )
	{
		$m = $matches[2];

		$m = str_replace('<p>', '', $m);
		$m = str_replace('</p>', '', $m);
		$m = str_replace("<br />", "<br>", $m);
		$m = str_replace("<br>", "MSO_N", $m);


		$m = htmlspecialchars($m, ENT_QUOTES);

		// для смайлов избежать конфликта
		$arr1 = array(':', '\'', '(', ')', '|', '-', '[', ']');
		$arr2 = array('&#58;', '&#39;', '&#40;', '&#41;', '&#124;', '&#45;', '&#91;', '&#93;');
		$m = str_replace($arr1, $arr2, $m);
		
		// ошибочный & перед < и >
		$m = str_replace('&amp;lt;', '&lt;', $m);
		$m = str_replace('&amp;gt;', '&gt;', $m);
		
		
		
		$text = "" . $matches[1] . $m . "</pre>\n";
	}
	else
		$text = $matches;

	$text = str_replace('<p>', '', $text);
	$text = str_replace('</p>', '', $text);

	return $text;
}


# подчистка PRE + mso_auto_tag
function mso_clean_pre($matches)
{
	if ( is_array($matches) )
		$text = "" . $matches[1] . $matches[2] . "</pre>\n";
	else
		$text = $matches;
		
	$text = str_replace('<p>', '', $text);
	$text = str_replace('</p>', '', $text);
	$text = str_replace('[', '&#91;', $text);
	$text = str_replace(']', '&#93;', $text);
	$text = str_replace("<br>", "MSO_N", $text);
	$text = str_replace("<br />", "<br>", $text);
	$text = str_replace("<br/>", "<br>", $text);
	$text = str_replace("<br>", "MSO_N", $text);
	
	$text = str_replace('<', '&lt;', $text);
	$text = str_replace('>', '&gt;', $text);
	$text = str_replace('&lt;pre', '<pre', $text);
	$text = str_replace('&lt;/pre', '</pre', $text);
	$text = str_replace('pre&gt;', 'pre>', $text);
	
	return $text;
}

# pre, которое загоняется в [html_base64]
function mso_clean_pre_do($matches)
{
	$text = trim($matches[2]);

	$text = str_replace('<p>', '', $text);
	$text = str_replace('</p>', '', $text);
	$text = str_replace('[', '&#91;', $text);
	$text = str_replace(']', '&#93;', $text);
	$text = str_replace("<br>", "MSO_N", $text);
	$text = str_replace("<br />", "<br>", $text);
	$text = str_replace("<br/>", "<br>", $text);
	$text = str_replace("<br>", "MSO_N", $text);
	
	$text = str_replace('<', '&lt;', $text);
	$text = str_replace('>', '&gt;', $text);
	$text = str_replace('&lt;pre', '<pre', $text);
	$text = str_replace('&lt;/pre', '</pre', $text);
	$text = str_replace('pre&gt;', 'pre>', $text);

	
	//pr($text);
	$text = $matches[1] . '[html_base64]' . base64_encode($text) . '[/html_base64]'. $matches[3];

	//_pr($text);
	return $text;
}


# подчистка блоковых тэгов
# удаляем в них <p>
function mso_clean_block($matches)
{

	if ( is_array($matches) )
		$text = "" . $matches[1] . $matches[2] . $matches[3] . "\n";
	else
		$text = $matches;

	$text = str_replace('<p>', '', $text);
	$text = str_replace('</p>', 'MSO_N_BLOCK', $text);
	$text = str_replace("<br>", "MSO_N", $text);
	$text = str_replace("<br />", "<br>", $text);
	$text = str_replace("<br/>", "<br>", $text);
	$text = str_replace("<br>", "MSO_N", $text);
	//$text = str_replace("\n", "MSO_N", $text);

	return $text;
}

# аналогично, только еще и [] меняем 
function mso_clean_block2($matches)
{

	if ( is_array($matches) )
		$text = "" . $matches[1] . $matches[2] . $matches[3] . "\n";
	else
		$text = $matches;

	$text = str_replace('<p>', '', $text);
	$text = str_replace('</p>', 'MSO_N_BLOCK', $text);
	$text = str_replace('[', '&#91;', $text);
	$text = str_replace(']', '&#93;', $text);
	$text = str_replace("<br />", "<br>", $text);
	$text = str_replace("<br/>", "<br>", $text);
	$text = str_replace("<br>", "MSO_N", $text);
	//$text = str_replace("\n", "MSO_N", $text);

	return $text;
}

# преобразуем введенный html в тексте между [html] ... [/html] и для [volkman]
# к обычному html
function mso_clean_html($matches)
{
	$arr1 = array('<p>', '</p>', '<br />',  '<br>',     '&amp;', '&lt;', '&gt;', "\n");
	$arr2 = array('',    '',     'MSO_N',   'MSO_N',   '&',     '<',    '>',    'MSO_N');

	$matches[1] = trim( str_replace($arr1, $arr2, $matches[1]) );

	return $matches[1];
}


# предподготовка html в тексте между [html] ... [/html]
# конвертируем все символы в реальный html
# после этого кодируем его в одну строчку base64
# после всех операций в mso_balance_tags декодируем его в обычный текст mso_clean_html_posle
# кодирование нужно для того, чтобы корректно пропустить весь остальной текст
function mso_clean_html_do($matches)
{
	$arr1 = array('&amp;', '&lt;', '&gt;', '<br />', '<br>', '&nbsp;');
	$arr2 = array('&',     '<',    '>',    "\n",     "\n",   ' ');
	
	$m = trim( str_replace($arr1, $arr2, $matches[1]) );

	$m = '[html_base64]' . base64_encode($m) . '[/html_base64]';

	return $m;
}


# декодирование из mso_balance_tags см. mso_clean_html_do
function mso_clean_html_posle($matches)
{
	return base64_decode($matches[1]);
}


# авторасстановка тэгов
function mso_auto_tag($pee, $pre_special_chars = false)
{
	$pee = str_replace(array("\r\n", "\r"), "\n", $pee);
	
	if ( mso_hook_present('content_auto_tag_custom') ) 
		return mso_hook('content_auto_tag_custom', $pee);
	
	$pee = mso_hook('content_auto_tag_do', $pee);

	if ( // отдавать как есть - слово в начале текста
		( strpos($pee, '[volkman]') !== false and strpos(trim($pee), '[volkman]') == 0 ) 
		or ( strpos($pee, '[source]') !== false and strpos(trim($pee), '[source]') == 0 ) 
	)
	{
		$pee = str_replace('[volkman]', '', $pee);
		$pee = str_replace('[source]', '', $pee);
		$pee = mso_clean_html( array('1'=>$pee) );
		$pee = str_replace('MSO_N', "\n", $pee);
		return $pee;
	}
	
	//pr($pee, true); # контроль
	
	$pee = str_replace("\n", "", $pee);
	$pee = $pee . "\n";

	# если html в [html] код [/html]
	$pee = str_replace('<p>[html]</p>', '[html]', $pee);
	$pee = str_replace('<p>[/html]</p>', '[/html]', $pee);
	$pee = preg_replace_callback('!\[html\](.*?)\[\/html\]!is', 'mso_clean_html_do', $pee );

	
	# исправляем стили браузера
	$pee = str_replace('<hr style="width: 100%; height: 2px;">', "<hr>", $pee);
	

	# всё приводим к MSO_N - признак переноса
	$pee = str_replace('<br />', '<br>', $pee);
	$pee = str_replace('<br/>', '<br>', $pee);
	$pee = str_replace('<br>', 'MSO_N', $pee);
	$pee = str_replace("\n", 'MSO_N', $pee); // все абзацы тоже <br>
	
	# удаляем двойные br
	$pee = str_replace('MSO_NMSO_NMSO_NMSO_N', 'MSO_N', $pee); 
	$pee = str_replace('MSO_NMSO_NMSO_N', 'MSO_N', $pee); 
	$pee = str_replace('MSO_NMSO_N', 'MSO_N', $pee); 
	

	# все MSO_N это абзацы
	$pee = str_replace('MSO_N', "\n", $pee); 
	
	
	# преформатированный текст
	$pee = preg_replace_callback('!(<pre.*?>)(.*?)(</pre>)!is', 'mso_clean_pre_do', $pee );
	
	
	# удалим перед всеми закрывающими тэгами абзац
	$pee = str_replace("\n</", "</", $pee); 

	//_pr($pee, true);
	
	# отбивка некоторых блоков
	//$pee = str_replace("<pre>", "\n<pre>\n", $pee); 
	//$pee = str_replace("</pre>", "\n</pre>\n", $pee);
	
	# расставим все абзацы по p
	$pee = preg_replace('!(.*)\n!', "\n<p>$1</p>", $pee);
	
	# исправим абзацы ошибочные
	$pee = str_replace("<p></p>", "", $pee); 
	$pee = str_replace("<p><p>", "<p>", $pee); 
	$pee = str_replace("</p></p>", "</p>", $pee); 
	
	# блочные тэги
	$allblocks = '(?:table|thead|tfoot|caption|colgroup|center|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|code|select|form|map|area|blockquote|address|math|style|input|embed|h1|h2|h3|h4|h5|h6|hr|p)';
	
	# здесь не нужно ставить <p> и </p>
	$pee = preg_replace('!<p>(<' . $allblocks . '[^>]*>)</p>!', "\n$1", $pee); # <p><tag></p>
	$pee = preg_replace('!<p>(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); # <p><tag> 
	$pee = preg_replace('!<p>(</' . $allblocks . '[^>]*>)!', "\n$1", $pee); # <p></tag> 
	$pee = preg_replace('!(<' . $allblocks . '[^>]*>)</p>!', "\n$1", $pee); # <tag></p>
	$pee = preg_replace('!(</' . $allblocks . '>)</p>!', "$1", $pee); # </tag></p>
	$pee = preg_replace('!(</' . $allblocks . '>) </p>!', "$1", $pee); # </tag></p>
	
	$pee = preg_replace('!<p>&nbsp;&nbsp;(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); # <p>&nbsp;&nbsp;<tag> 
	$pee = preg_replace('!<p>&nbsp;(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); # <p>&nbsp;<tag> 
	
	# если был cut, то уберем с ссылки абзац
	$pee = str_replace('<p><a id="cut"></a></p>', '<a id="cut"></a>', $pee); 
	
	# специфичные ошибки
	$pee = str_replace("<blockquote>\n<p>", "<blockquote>", $pee); 
	$pee = preg_replace('!<li>(.*)</p>\n!', "<li>$1</li>\n", $pee); # <li>...</p>
	
	
	$pee = preg_replace('!<p><a id="(.*)"></a></p>\n!', "<a id=\"$1\"></a>\n", $pee); # <li>...</p>
	
	
	### подчистим некоторые блочные тэги удалим <p> внутри. MSO_N_BLOCK = </p>
	
	# code
	$pee = preg_replace_callback('!(<code.*?>)(.*?)(</code>)!is', 'mso_clean_block2', $pee );
	$pee = str_replace('MSO_N_BLOCK', "<br>", $pee); // заменим перенос в блочном на <br> 
	
	# blockquote
	$pee = preg_replace_callback('!(<blockquote.*?>)(.*?)(</blockquote>)!is', 'mso_clean_block', $pee );
	$pee = str_replace('MSO_N_BLOCK', "<br>", $pee); // заменим перенос в блочном на ''	
	
	
	# еще раз подчистка
	$pee = str_replace('MSO_N', "\n", $pee); 
	
	# введём bb-код [br]
	$pee = str_replace('[br]', '<br>', $pee);

	$pee = preg_replace('!<p><br(.*)></p>!', "<br$1>", $pee); # <br clear="all">
	$pee = preg_replace('!<p><br></p>!', "<br>", $pee); # <br clear="all">
	
		
	# завершим [html]
	$pee = str_replace('<p>[html_base64]', '[html_base64]', $pee);
	$pee = str_replace('[/html_base64]</p>', '[/html_base64]', $pee);
	$pee = str_replace('[/html_base64] </p>', '[/html_base64]', $pee);
	
	$pee = preg_replace_callback('!\[html_base64\](.*?)\[\/html_base64\]!is', 'mso_clean_html_posle', $pee );
	
	# перенос строки в конце текста
	$pee = $pee . "\n";
	
	# кастомный автотэг
	$pee = mso_hook('content_auto_tag_my', $pee);
	
	// _pr($pee, true); # контроль

	return $pee;

}

# моя функция авторасстановки тэгов
function mso_balance_tags($text)
{
	if ( mso_hook_present('content_balance_tags_custom') ) return $text = mso_hook('content_balance_tags_custom', $text);
	
	$text = mso_hook('content_balance_tags_my', $text);
	
	return $text;
}


# функция преобразует русские и украинские буквы в английские
# также удаляются все служебные символы
function mso_slug($slug)
{
	$slug = mso_hook('slug_do', $slug);

	if (!mso_hook_present('slug'))
	{
		// таблица замены
		$repl = array(
		"А"=>"a", "Б"=>"b",  "В"=>"v",  "Г"=>"g",   "Д"=>"d",
		"Е"=>"e", "Ё"=>"jo", "Ж"=>"zh",
		"З"=>"z", "И"=>"i",  "Й"=>"j",  "К"=>"k",   "Л"=>"l",
		"М"=>"m", "Н"=>"n",  "О"=>"o",  "П"=>"p",   "Р"=>"r",
		"С"=>"s", "Т"=>"t",  "У"=>"u",  "Ф"=>"f",   "Х"=>"h",
		"Ц"=>"c", "Ч"=>"ch", "Ш"=>"sh", "Щ"=>"shh", "Ъ"=>"",
		"Ы"=>"y", "Ь"=>"",   "Э"=>"e",  "Ю"=>"ju", "Я"=>"ja",

		"а"=>"a", "б"=>"b",  "в"=>"v",  "г"=>"g",   "д"=>"d",
		"е"=>"e", "ё"=>"jo", "ж"=>"zh",
		"з"=>"z", "и"=>"i",  "й"=>"j",  "к"=>"k",   "л"=>"l",
		"м"=>"m", "н"=>"n",  "о"=>"o",  "п"=>"p",   "р"=>"r",
		"с"=>"s", "т"=>"t",  "у"=>"u",  "ф"=>"f",   "х"=>"h",
		"ц"=>"c", "ч"=>"ch", "ш"=>"sh", "щ"=>"shh", "ъ"=>"",
		"ы"=>"y", "ь"=>"",   "э"=>"e",  "ю"=>"ju",  "я"=>"ja",

		# украина
		"Є" => "ye", "є" => "ye", "І" => "i", "і" => "i",
		"Ї" => "yi", "ї" => "yi", "Ґ" => "g", "ґ" => "g",
		
		# беларусь
		"Ў"=>"u", "ў"=>"u", "'"=>"",

		"«"=>"", "»"=>"", "—"=>"-", "`"=>"", " "=>"-",
		"["=>"", "]"=>"", "{"=>"", "}"=>"", "<"=>"", ">"=>"",

		"?"=>"", ","=>"", "*"=>"", "%"=>"", "$"=>"",

		"@"=>"", "!"=>"", ";"=>"", ":"=>"", "^"=>"", "\""=>"",
		"&"=>"", "="=>"", "№"=>"", "\\"=>"", "/"=>"", "#"=>"",
		"("=>"", ")"=>"", "~"=>"", "|"=>"", "+"=>"", "”"=>"", "“"=>"",
		"'"=>"",

		"’"=>"",
		"—"=>"-", // mdash (длинное тире)
		"–"=>"-", // ndash (короткое тире)
		"™"=>"tm", // tm (торговая марка)
		"©"=>"c", // (c) (копирайт)
		"®"=>"r", // (R) (зарегистрированная марка)
		"…"=>"", // (многоточие)
		"“"=>"",
		"”"=>"",
		"„"=>"",

		);

		$slug = strtolower(strtr(trim($slug), $repl));

		# разрешим расширение .html
		$slug = str_replace('.htm', '@HTM@', $slug);
		$slug = str_replace('.', '', $slug);
		$slug = str_replace('@HTM@', '.htm', $slug);

		$slug = str_replace('---', '-', $slug);
		$slug = str_replace('--', '-', $slug);

		$slug = str_replace('-', ' ', $slug);
		$slug = str_replace(' ', '-', trim($slug));
	}
	else $slug = mso_hook('slug', $slug);

	$slug = mso_hook('slug_posle', $slug);
	
	return $slug;
}


# редирект на страницу сайта. путь указывать относительно сайта
# если $absolute = true - переход по указаному пути
# $header - 301 или 302 редирект
function mso_redirect($url = '', $absolute = false, $header = false)
{
	global $MSO;

	$url = strip_tags($url);
	$url = str_replace( array('%0d', '%0a'), '', $url );
	
	$url = mso_xss_clean($url);
	
	if ($header == 301) header('HTTP/1.1 301 Moved Permanently');
	elseif ($header == 302) header('HTTP/1.1 302 Found'); 

	if ($absolute)
	{
		header("Refresh: 0; url={$url}");
		header("Location: {$url}");
	}
	else
	{
		$url = $MSO->config['site_url'] . $url;
		header("Refresh: 0; url={$url}");
		header("Location: {$url}");
	}
	exit();
}


# получение текущего url относительно сайта
# ведущий и конечные слэши удаляем
# если $absolute = true, то возвращается текущий урл как есть
function mso_current_url($absolute = false)
{
	global $MSO;

	$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") ? "https://" : "http://";
	$url .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
	
	if ($absolute) return $url;
	
	$url = str_replace($MSO->config['site_url'], "", $url);

	$url = trim( str_replace('/', ' ', $url) );
	$url = str_replace(' ', '/', $url);

	return $url;
}


# формируем скрытый input для формы с текущей сессией
function mso_form_session($name_form = 'flogin_session_id')
{
	global $MSO;

	return '<input type="hidden" value="' . $MSO->data['session']['session_id'] . '" name="' . $name_form . '">';
}


# вывод логин-формы
# в login_form_auth_title для разделителей используется код [end] - нужен для того, чтобы перечислять через запятую
function mso_login_form($conf = array(), $redirect = '', $echo = true)
{
	global $MSO;

	if ($redirect == '') $redirect = urlencode(mso_current_url());

	$login = (isset($conf['login'])) ? $conf['login'] : '';
	$password = (isset($conf['password'])) ? $conf['password'] : '';
	$submit = (isset($conf['submit'])) ? $conf['submit'] : '';
	$submit_value = (isset($conf['submit_value'])) ? $conf['submit_value'] : t('Войти');
	$form_end = (isset($conf['form_end'])) ? $conf['form_end'] : '';
	
	$login_form_auth_title = (isset($conf['login_form_auth_title'])) ? $conf['login_form_auth_title'] : t('Вход через:') . ' ';

	$action = $MSO->config['site_url'] . 'login';
	$session_id = $MSO->data['session']['session_id'];
	
	$hook_login_form_auth = mso_hook_present('login_form_auth') ? '<span class="login-form-auth-title">' . $login_form_auth_title . '</span>' . mso_hook('login_form_auth') : '';
	
	if ($hook_login_form_auth)
	{
		$hook_login_form_auth = trim(str_replace('[end]', '     ', $hook_login_form_auth));
		$hook_login_form_auth = '<div class="login-form-auth">' . str_replace('     ', ', ', $hook_login_form_auth) . '</div>';
	}
	
	$out = <<<EOF
	<form method="post" action="{$action}" name="flogin" class="flogin">
		<input type="hidden" value="{$redirect}" name="flogin_redirect">
		<input type="hidden" value="{$session_id}" name="flogin_session_id">
		<p class="flogin_user"><label class="flogin_user"><span>{$login}</span><input type="text" value="" name="flogin_user" class="flogin_user"></label></p>
		<p class="flogin_password"><label class="flogin_password"><span>{$password}</span><input type="password" value="" name="flogin_password" class="flogin_password"></label></p>
		<p class="flogin_submit">{$submit}<input type="submit" name="flogin_submit" class="flogin_submit" value="{$submit_value}"></p>
		{$hook_login_form_auth}
		{$form_end}
	</form>
EOF;
	if ($echo) echo $out;
		else return $out;
}


# посыл в хидере no-кэш
# кажется не работает - как проверить хз...
function mso_nocache_headers()
{
	# см. http://www.nomagic.ru/all.php?aid=58
	@header("Cache-Control: no-store, no-cache, must-revalidate"); 
	@header("Expires: " . date("r")); 
	
	// @header('Expires: Wed, 11 Jan 1984 05:00:00 GMT');
	// @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
	// @header('Cache-Control: no-cache, must-revalidate, max-age=0');
	// @header('Pragma: no-cache');
}


# функция проверяет существование POST, а также обязательных полей
# которые передаются в массиве ( array('f_session_id','f_desc','f_edit_submit') )
# если полей нет, то возвращается false
# если поля есть, то возвращается $_POST
function mso_check_post($args = array())
{
	if ($_POST)
	{
		$check = true;
		foreach ($args as $key=>$field)
		{
			if (!isset($_POST[$field]))
			{  // нет значения - выходим
				$check = false;
				break;
			}
		}
		if (!$check) return false;
			else return $_POST;
	}
	else return false;
}


# получение из массива номера $num_key ключа
# array('2'=>'Изменить');
# возвратит 2
function mso_array_get_key($ar, $num_key = 0, $no = false)
{
	$ar = array_keys($ar);

	if (isset($ar[$num_key])) return $ar[$num_key];
		else return $no;
}


# получение из массива ключ значения
# array('2'=>'Изменить');
# mso_array_get_key_value($ar, 'Изменить' ) возвратит 2
function mso_array_get_key_value($ar, $value = false, $no = false)
{
	if (!$value) return $no;
	if (!in_array($value, $ar)) return $no;

	foreach ($ar as $key=>$val)
	{
		if ($val == $value)	return $key;
	}
}


# проверка комбинации логина-пароля
# если указан act - то сразу смотрим разрешение на действие
function mso_check_user_password($login = false, $password = false, $act = false)
{
	if (!$login or !$password) return false;

	$CI = & get_instance();

	$CI->db->select('users_id, users_groups_id');
	$CI->db->where(array('users_login'=>$login, 'users_password'=>$password) );  // where 'users_password' = $password
	$CI->db->limit(1); # одно значение

	$query = $CI->db->get('users');
	if ($query->num_rows() > 0) # есть такой юзер
	{
		if ($act)
		{
			// нужно проверить по users_groups_id разрешение для этого юзера для этого действия
			$r = $query->result_array();
			return mso_check_allow($act, $r[0]['users_id']);
		}
		else return true; // если act не указан, значит можно
	}
	else return false;
}


# получаем данные юзера по его логину/паролю
function mso_get_user_data($login = false, $password = false)
{
	if (!$login or !$password) return false;

	$CI = & get_instance();
	$CI->db->select('*');
	$CI->db->limit(1); # одно значение
	$CI->db->where('users_login', $login); // where 'users_login' = $login
	$CI->db->where('users_password', $password);  // where 'users_password' = $password

	$query = $CI->db->get('users');

	if ($query->num_rows() > 0) # есть такой юзер
	{
		$r = $query->result_array();
		return $r[0];
	}
	else return false;
}


# содание разрешения для действия
function mso_create_allow($act = '', $desc = '')
{
	global $MSO;

	// считываем опцию
	$d = mso_get_option('groups_allow', 'general');

	if (!$d) // нет таких опций вообще
	{
		$d = array($act => $desc); // создаем массив
		mso_add_option ('groups_allow', $d, 'general'); // добавляем опции
		return;
	}
	else // есть опции
	{
		if ( isset($d[$act]) and ($d[$act] == $desc)) return; // ничего не изменилось
		else
		{	// что-то новенькое
			$d[$act] = $desc; // добавляем
			mso_add_option ('groups_allow', $d, 'general');
			return;
		}
	}
}


# удалить действие/функцию
function mso_remove_allow($act = '')
{
	global $MSO;

	$d = mso_get_option('groups_allow', 'general');

	if ( isset($d[$act]) )
	{
		unset($d[$act]);
		mso_delete_option('groups_allow', 'general');
		mso_add_option ('groups_allow', $d, 'general');
	}
}


# проверка доступа для юзера для указанного действия/функции
# если $cache = true то данные можно брать из кэша, иначе всегда из SQL
function mso_check_allow($act = '', $user_id = false, $cache = true)
{
	global $MSO;

	if (!$act) return false;

	if ( $user_id == false ) // если юзер не указан
	{
		if (! $MSO->data['session']['users_id']) // и в сесии
			return false;
		else
			$user_id = $MSO->data['session']['users_id']; // берем его номер из сессии

		if ( $MSO->data['session']['users_groups_id'] == '1' ) // отмечена первая группа - это админы
		{
			return true; // админам всё можно
		}
	}
	else
		$user_id = (int) $user_id; // юзер указан явно - нужно проверять
	// если есть кэш этого участника, где уже хранятся его разрешения
	// то берем кэш, если нет, то выполняем запрос полностью

	if ($cache)	$k = mso_get_cache('user_rules_' . $user_id );
		else $k = false;

	if (!$k) // нет кэша
	{
		// по номеру участника получаем номер группы
		// по номеру группы получаем все разрешения этой группы

		$CI = & get_instance();
		$CI->db->select('users_groups_id, groups_rules, groups_id');// groups_name
		$CI->db->limit(1);
		$CI->db->where('users_id', $user_id);
		$CI->db->from('users');
		$CI->db->join('groups', 'groups.groups_id = users.users_groups_id');

		$query = $CI->db->get();

		foreach ($query->result_array() as $rw)
		{
			$rules = $rw['groups_rules'];
			$groups_id = $rw['groups_id'];
		}

		if ($groups_id == 1) return true; // админам можно всё

		$rules = unserialize($rules); // востанавливаем массив с разрешениями этой группы
		mso_add_cache('user_rules_' . $user_id, $rules); // сразу в кэш добавим
	}
	else // есть кэш
	{
		$rules = $k;
	}

	/*
	$rules = Array (
		[edit_users_group] => 1
		[edit_users_admin_note] => 1
		[edit_other_users] => 1
		[edit_self_users] => 1 )
	*/

	if (isset( $rules[$act] )) // если действие есть в массиве
	{
		if ($rules[$act] == 1) return true; // и разрешено
			else return false; // запрещено
	}
	else return false; // действия вообще нет в разрешениях
}


# получаем название указанного сегменту текущей страницы
# http://localhost/admin/users/edit/1
# mso_segment(3) -> edit
# номер считается от home-сайта
# если в сегменте находится XSS и $die = true, то рубим все
# можно проверить сегмент в своём массиве $my_segments
function mso_segment($segment = 2, $die = true, $my_segments = false)
{
	global $MSO;
	
	$CI = & get_instance();
	
	if ($my_segments === false) $my_segments = $MSO->data['uri_segment'];
	
	if ( count($my_segments) > ($segment - 1) )
		$seg = $my_segments[$segment];
	else $seg = '';
	
	$seg = urldecode($seg);
	
	// $url = $CI->input->xss_clean($seg); // ci < 2
	$url = $CI->security->xss_clean($seg, false);
	
	if ($url != $seg and $die) die('<b><font color="red">Achtung! XSS attack!</font></b>');
	
	return $url;
}


# функция преобразования MySql-даты (ГГГГ-ММ-ДД ЧЧ:ММ:СС) в указанный формат date
# идея - http://dimoning.ru/archives/31
# $days и $month - массивы или строка (через пробел) названия дней недели и месяцев
function mso_date_convert($format = 'Y-m-d H:i:s', $data, $timezone = true, $days = false, $month = false)
{
	$res = '';
	$part = explode(' ' , $data);

	if (isset($part[0])) $ymd = explode ('-', $part[0]);
		else $ymd = array (0,0,0);

	if (isset($part[1])) $hms = explode (':', $part[1]);
		else $hms = array (0,0,0);

	$y = $ymd[0];
	$m = $ymd[1];
	$d = $ymd[2];
	$h = $hms[0];
	$n = $hms[1];
	$s = $hms[2];

	$time = mktime($h, $n, $s, $m, $d, $y);

	if ($timezone)
	{
		if ($timezone === -1) // в случаях, если нужно убрать таймзону
			$time = $time - getinfo('time_zone') * 60 * 60;
		else
			$time = $time + getinfo('time_zone') * 60 * 60;
	}

	$out = date($format, $time);

	if ($days)
	{
		if (!is_array($days)) $days = explode(' ', trim($days));

		$day_en = array('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');
		$out = str_replace($day_en, $days, $out);

		$day_en = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');
		$out = str_replace($day_en, $days, $out);
	}
	if ($month)
	{
		if (!is_array($month)) $month = explode(' ', trim($month));
		
		//$out = str_replace(' ', '_', $out);
		
		$month_en = array('January', 'February', 'March', 'April', 'May', 'June', 'July',	'August', 'September', 'October', 'November', 'December');
		$out = str_replace($month_en, $month, $out);
		
		# возможна ситуация, когда стоит английский язык, поэтому следующая замена приведет к ошибке
		# поэтому заменим $month_en на что-то своё
		
		
		$out = str_replace($month_en, array('J_anuary', 'F_ebruary', 'M_arch', 'A_pril', 'M_ay', 'J_une', 'J_uly',	'A_ugust', 'S_eptember', 'O_ctober', 'N_ovember', 'D_ecember'), $out);
		
		$month_en = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
		$out = str_replace($month_en, $month, $out);
		
		# теперь назад
		$out = str_replace(
				array('J_anuary', 'F_ebruary', 'M_arch', 'A_pril', 'M_ay', 'J_une', 'J_uly',	'A_ugust', 'S_eptember', 'O_ctober', 'N_ovember', 'D_ecember'), 
				array('January', 'February', 'March', 'April', 'May', 'June', 'July',	'August', 'September', 'October', 'November', 'December'), $out);
		
		
	}

	return $out;
}


# переобразование даты в формат MySql
function mso_date_convert_to_mysql($year = 1970, $mon = 1, $day = 1, $hour = 0, $min = 0, $sec = 0)
{
	if ($day>31)
	{
		$day = 1;
		$mon ++;
		$year ++;
	}

	if ($mon>12)
	{
		$mon = 1;
		$year ++;
	}

	if ( $mon < 10 ) $mon = '0' . $mon;
	if ( $day < 10 ) $day = '0' . $day;
	if ( $hour < 10 ) $hour = '0' . $hour;
	if ( $min < 10 ) $min = '0' . $min;
	if ( $sec < 10 ) $sec = '0' . $sec;

	$res = $year . '-' . $mon . '-' . $day . ' ' . $hour . ':' . $min . ':' . $sec;
	return $res;
}


# получить пермалинк страницы по её id
# через запрос БД
function mso_get_permalink_page($id = 0, $prefix = 'page/')
{
	global $MSO;
	$id = (int) $id;
	if (!$id) return '';

	$CI = & get_instance();
	$CI->db->select('page_slug, page_id');
	$CI->db->where(array('page_id'=>$id));

	$query = $CI->db->get('page');

	if ($query->num_rows()>0)
	{
		foreach ($query->result_array() as $row)
			$slug = $row['page_slug'];

		return  $MSO->config['site_url'] . $prefix . $slug;
	}
	else return '';
}


# получить пермалинк рубрики по указанному слагу
function mso_get_permalink_cat_slug($slug = '', $prefix = 'category/')
{
	if (!$slug) return '';
	return  getinfo('siteurl') . $prefix . $slug;
}


#  разделить строку из чисел, разделенных запятыми в массив
# если $integer = true, то дополнительно преобразуется в числа
# если $probel = true, то разделителем может быть пробел
# если $unique = true, то убираем дубли
function mso_explode($s = '', $int = true, $probel = true, $unique = true )
{
	//$s = trim( str_replace(',', ',', $s) );
	$s = trim( str_replace(';', ',', $s) );
	if ($probel)
	{
		$s = trim( str_replace('     ', ',', $s) );
		$s = trim( str_replace('    ', ',', $s) );
		$s = trim( str_replace('   ', ',', $s) );
		$s = trim( str_replace('  ', ',', $s) );
		$s = trim( str_replace(' ', ',', $s) );
	}

	$s = trim( str_replace(',,', ',', $s) );
	
	$s = explode(',', trim($s));
	if ($unique) $s = array_unique($s);

	$out = array();
	foreach ( $s as $key => $val )
	{
		if ($int)
		{
			if (  (int) $val > 0 ) $out[] = $val; // id в массив
		}
		else
		{
			if (trim($val)) $out[] = trim($val);
		}
	}

	if ($unique) $out = array_unique($out);

	return $out;
}


#  обрезаем строку на кол-во слов
function mso_str_word($text, $counttext = 10, $sep = ' ')
{
	$words = explode($sep, $text);
	if ( count($words) > $counttext )
		$text = join($sep, array_slice($words, 0, $counttext));
	return $text;
}


# подсчет кол-ва слов в тексте
# можно предварительно удалить все тэги и преобразовать CR в $delim
function mso_wordcount($str = '', $delim = ' ', $strip_tags = true, $cr_to_delim = true)
{
	if ($strip_tags) $str = strip_tags($str);
	if ($cr_to_delim) $str = str_replace("\n", $delim, $str);

	$out = str_replace($delim . $delim, $delim, $str);

	return count( explode($delim, $str) );
}


# получить текущую страницу пагинации
# next - признак сегмент после которого указывается номер страницы
function mso_current_paged($next = 'next')
{
	global $MSO;

	$uri = $MSO->data['uri_segment'];

	if ($n = mso_array_get_key_value($uri, $next))
	{
		if (isset($uri[$n+1])) $n = (int) $uri[$n+1];
			else $n = 1;

		if ($n > 0) $current_paged = $n;
			else $current_paged = 1;
	}
	else $current_paged = 1;

	return $current_paged;
}

# увеличение или уменьшение ссылки next на указанную величину $inc
# http://site.com/home/next/2 -> +1 -> http://site.com/home/next/3
# $url - исходный адрес (относительно сайта). Если адрес = пустой строке, 
#	то берем mso_current_url Если первый сегмент пустой, то это home
# $inc - величина изменения
# $max - максимум - если $inc + текущий > $max, то ставится $max. Если $max = false, то он не учитывается
# $min - минимальное значение
# $next - признак сегмент после которого указывается номер страницы
# $empty_no_range = true - отдает пустую строчку, если текущая paged будет равна конечной
# если $empty_no_range = false, то отдаем ссылку как обычно
function mso_url_paged_inc($max = false, $inc = 1, $empty_no_range = true, $url = '', $min = 1, $next = 'next')
{
	if (!$url) $url = mso_current_url();
	
	$current_paged = mso_current_paged($next);
	$result_paged = $current_paged + $inc;
	
	if ($max) if ($result_paged > $max) $result_paged = $max;
	
	if ($result_paged < $min) $result_paged = $min;
	
	if ($empty_no_range and $result_paged == $current_paged ) return '';
	
	// если нет $url, то это главная
	if (!$url) $url = 'home/' . $next . '/' . $current_paged;
	
	if ( strpos($url, $next . '/') === false ) // нет вхождения next/ - нужно добавить
		$url .= '/' . $next . '/' . $current_paged;
	
	// если $result_paged = , то $min, то	$next не пишем
	if ($result_paged == $min)
		$url = str_replace($next . '/' . $current_paged, '', $url);
	else
		$url = str_replace($next . '/' . $current_paged, $next . '/' . $result_paged, $url);
	
	
	// удалим последние слэши
	$url = trim(str_replace('/', ' ', $url));
	$url = str_replace(' ', '/', $url);
	
	if ($url == 'home') $url = '';
	
	return getinfo('siteurl') . $url;
}

# регистрируем сайдбар
function mso_register_sidebar($sidebar = '1', $title = 'Cайдбар', $options = array() )
{
	global $MSO;

	$MSO->sidebars[$sidebar] = array('title' => t($title), 'options' => $options);
}


# регистрируем виджет
function mso_register_widget($widget = false, $title = 'Виджет')
{
	global $MSO;

	if ($widget) $MSO->widgets[$widget] = t($title);
}


# вывод сайбрара
function mso_show_sidebar($sidebar = '1', $block_start = '', $block_end = '', $echo = true)
{
	global $MSO, $page; // чтобы был доступ к параметрам страниц в условиях виджетов

	static $num_widget = array(); // номер виджета по порядку в одном сайдбаре

	$widgets = mso_get_option('sidebars-' . $sidebar, 'sidebars', array());

	$out = '';

	if ($widgets) // есть виджеты
	{
		foreach ($widgets as $widget)
		{
			$usl_res = 1; // предполагаем, что нет условий, то есть всегда true

			// имя виджета может содержать номер через пробел
			$arr_w = explode(' ', $widget); // в массив
			if ( sizeof($arr_w) > 1 ) // два или больше элементов
			{
				$widget = trim( $arr_w[0] ); // первый - функция
				$num = trim( $arr_w[1] ); // второй - номер виджета

				if (isset($arr_w[2])) // есть какое-то php-условие
				{
					$u = $arr_w; // поскольку у нас разделитель пробел, то нужно до конца все подбить в одну строчку
					$u[0] = $u[1] = '';
					$usl = trim(implode(' ', $u));

					// текст условия, is_type('home') or is_type('category')
					$usl = 'return ( ' . $usl . ' ) ? 1 : 0;';
					$usl_res = eval($usl); // выполяем
					if ($usl_res === false) $usl_res = 1; // возможно произошла ошибка
				}
			}
			else
			{
				$num = 0; // номер виджета не указан, значит 0
			}
			
			// номер функции виджета может быть не только числом, но и текстом
			// если текст, то нужно его преобразовать в slug, чтобы исключить 
			// некоректную замену [NUMF] для стилей
			$num = mso_slug($num);
			
			// двойной - заменим на один - защита id в форме админки
			$num = str_replace('--', '-', $num);
			
			if ( function_exists($widget) and $usl_res === 1)
			{
				if ($temp = $widget($num)) // выполняем виджет если он пустой, то пропускаем вывод
				{
					if (isset($num_widget[$sidebar]['numw'])) //уже есть номер виджета
					{
						$numw = ++$num_widget[$sidebar]['numw'];
						$num_widget[$sidebar]['numw'] = $numw;
					}
					else // нет такого = пишем 1
					{
						$numw = $num_widget[$sidebar]['numw'] = 1;
					}
					
					$st = str_replace('[FN]', $widget, $block_start); // название функции виджета
					$st = str_replace('[NUMF]', $num, $st); // номер функции
					$st = str_replace('[NUMW]', $numw, $st);	//
					$st = str_replace('[SB]', $sidebar, $st); // номер сайдбара

					$en = str_replace('[FN]', $widget, $block_end);
					$en = str_replace('[NUMF]', $num, $en);
					$en = str_replace('[NUMW]', $numw, $en);
					$en = str_replace('[SB]', $sidebar, $en);
					
					// обрамим содержимое виджета в div.widget-content
					if (stripos($temp, '</span></h2>') !== false)
					{
						// есть вхождение заголовка виджета <h2>
						$temp = str_replace('</span></h2>', '</span></h2><div class="widget-content">', $temp);
						$en = '</div>' . $en;
					}
					else
					{
						$temp = '<div class="widget-content">' . $temp . '</div>';
					}
					
					$out .= $st . $temp . $en;
				}
			}
		}
		
		if ($echo) echo $out;
		else return $out;
	}
}


# вспомогательная функция, которая принимает глобальный _POST
# и поле $option. Использовать в _update виджетов
function mso_widget_get_post($option = '')
{
	if ( isset($_POST[$option]) )
	    return stripslashes($_POST[$option]);
	else return '';
}


# функция отправки письма по email
# preferences пока не реализована
function mso_mail($email = '', $subject = '', $message = '', $from = false, $preferences = array())
{
	
	$arg = array('email' => $email, 'subject' => $subject, 'message' => $message, 'from' => $from, 'preferences' => $preferences);
	
	# если определен хук mail, то через него отправляем данные
	if (mso_hook_present('mail'))
	{
		return mso_hook('mail', $arg);
	}
	
	$CI = & get_instance();
	$CI->load->library('email');
	
	$CI->email->clear(true);
	
	if (isset($preferences['attach']) and trim($preferences['attach']))
	{
		$CI->email->attach($preferences['attach']);
	}
		
	if ($from) $admin_email = $from;
		else $admin_email = mso_get_option('admin_email_server', 'general', 'admin@site.com');
	
	$config['wordwrap'] = isset($preferences['wordwrap']) ? $preferences['wordwrap'] : TRUE;
	$config['wrapchars'] = isset($preferences['wrapchars']) ? $preferences['wrapchars'] : 90;
	
	# можно отправлять письмо в html-формате
	if (isset($preferences['mailtype']) and $preferences['mailtype'])
		$config['mailtype'] = $preferences['mailtype'];
	
	$CI->email->initialize($config);

	$CI->email->to($email);
	
	if (isset($preferences['from_name']))
		$CI->email->from($admin_email, $preferences['from_name']);
	else	
		$CI->email->from($admin_email, getinfo('name_site'));
	
	$CI->email->subject($subject);
	$CI->email->message($message);
	$CI->email->_safe_mode = true; # иначе CodeIgniter добавляет -f к mail - не будет работать в не safePHP

	$res = @$CI->email->send();
	
	# mail($email, $subject, $message); # проверка
	if (!$res) 
	{
		if (isset($preferences['print_debugger']) and $preferences['print_debugger'])
		{
			echo $CI->email->print_debugger();
		}
	}
	
	$arg['res'] = $res;
	mso_hook('mail_res', $arg); // хук, если нужно отслеживать отправку почты
	
	return $res;
}


# для юникода отдельный wordwrap
# часть кода отсюда: http://us2.php.net/manual/ru/function.wordwrap.php#78846
# переделал и исправил ошибки я
# ширина, разделитель
function mso_wordwrap($str, $wid = 80, $tag = ' ')
{
		$pos = 0;
		$tok = array();
		$l = mb_strlen($str, 'UTF8');

		if($l == 0) return '';

		$flag = false;

		$tok[0] = mb_substr($str, 0, 1, 'UTF8');

		for($i = 1 ; $i < $l ; ++$i)
		{
				$c = mb_substr($str, $i, 1, 'UTF8');

				if(!preg_match('/[a-z\'\"]/i',$c))
				{
						++$pos;
						$flag = true;
				}
				elseif($flag)
				{
						++$pos;
						$flag = false;
				}

				if (isset($tok[$pos])) $tok[$pos] .= $c;
					else $tok[$pos] = $c;
		}

		$linewidth = 0;
		$pos = 0;
		$ret = array();
		$l = count($tok);
		for($i = 0 ; $i < $l ; ++$i)
		{
				if($linewidth + ($w = mb_strwidth($tok[$i], 'UTF8') ) > $wid)
				{
						++$pos;
						$linewidth = 0;
				}
				if (isset($ret[$pos])) $ret[$pos] .= $tok[$i];
					else $ret[$pos] = $tok[$i];

				$linewidth += $w;
		}
		return implode($tag, $ret);
}


# возвращает script с jquery или +url
function mso_load_jquery($plugin = '')
{
	global $MSO;

	if ( !isset($MSO->js['jquery'][$plugin]) ) // еще нет включения этого плагина
	{
		$MSO->js['jquery'][$plugin] = '1';
		if ($plugin)
			return '<script type="text/javascript" src="'. getinfo('common_url') . 'jquery/' . $plugin . '"></script>' . NR;
		else
			return '<script type="text/javascript" src="'. getinfo('common_url') . 'jquery/jquery-1.6.min.js"></script>' . NR;
	}
}


# формируем li-элементы для меню
# элементы представляют собой текст, где каждая строчка один пункт
# каждый пункт делается так:  http://ссылка|название
# на выходе так:
# <li class="selected"><a href="url"><span>ссылка</span></a></li>
# если первый символ [ то это открывает группу ul 
# если ] то закрывает - позволяет создавать многоуровневые меню
function mso_menu_build($menu = '', $select_css = 'selected', $add_link_admin = false)
{
	# добавить ссылку на admin
	if ($add_link_admin and is_login()) $menu .= NR . 'admin|Admin';

	$menu = str_replace("\r", "", $menu); // если это windows
	$menu = str_replace("_NR_", "\n", $menu);
	$menu = str_replace("\n\n\n", "\n", $menu);
	$menu = str_replace("\n\n", "\n", $menu);

	# в массив
	$menu = explode("\n", trim($menu));
	
	# обработаем меню на предмет пустых строк, корректности и подсчитаем кол-во элементов
	$count_menu = 0;
	foreach ($menu as $elem)
	{
		if (strlen(trim($elem)) > 1) $count_menu++;
	}

	# определим текущий url
	$http = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") ? "https://" : "http://";
	$current_url = $http . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];

	$out = '';
	# обходим в цикле
	
	$i = 1; // номер пункта
	$n = 0; // номер итерации цикла 
	
	$group_in = false;
	$group_in_first = false;
	$group_num = 0; // номер группы
	$group_work = false; // открытая группа?
	$selected_present = false; // есть ли выделеный пункт?
	$group_elem = 0; // элемент в группе
	
	foreach ($menu as $elem)
	{
		# разобъем строчку по адрес | название
		$elem = explode('|', $elem);
		
		# должно быть два элемента
		if (count($elem) > 1 )
		{
			$url = trim($elem[0]);  // адрес
			$name = trim($elem[1]); // название
			
			if (isset($elem[2])) $title = ' title="' . htmlspecialchars(trim($elem[2])) . '"';
			else $title = '';
			
			if (($url != '#') and strpos($url, $http) === false) // нет в адресе http:// - значит это текущий сайт
			{
				if ($url == '/') $url = getinfo('siteurl'); // это главная
					else $url = getinfo('siteurl') . $url;
			}

			# если текущий адрес совпал, значит мы на этой странице
			if ($url == $current_url)
			{
				$class = ' ' . $select_css;
				$selected_present = true;
			}
			else $class = '';

			# для первого элемента добавляем класс first
			if ($i == 1) $class .= ' first';
			
			if ($group_in_first)
 			{
				$class .= ' group-first';
 				$group_in_first = false;
			}
 				
			# для последнего элемента добавляем класс last
			if ($i == $count_menu) $class .= ' last';

			if ($class == ' ') $class = '';
			
			if ($group_in) // открываем группу
			{
				$group_num++;
				$class .= ' group-num-' . $group_num;
				
				$out .= '<li class="group' . $class . '"><a href="' . $url . '"' . $title . '><span>' . $name . '</span></a>' 
						. NR . '<ul>' . NR;
				
				$group_in = false;
				$group_in_first = true;
			}
			else
			{
				if ($group_elem > 0 and array_key_exists($i, $menu) and isset($menu[$n+1]) and trim($menu[$n+1]) == ']' ) $class .= ' group-last';
				
				$out .= '<li class="' . trim($class) . '"><a href="' . $url . '"' . $title . '><span>' . $name . '</span></a></li>' . NR;
			}
			
			
			if ($url == $current_url and $group_work) // выделяем родителя группы, если в ней выделенный подпункт
			{
				$out = str_replace('group-num-' . $group_num, 'group-num-' . $group_num . ' group-selected', $out);	
				$selected_present = true;
			}
			
			$i++;
			$group_elem++;
		}
		else
		{
			// если это [, то это начало группы ul 
			// если ] то /ul

			if ($elem[0] == '[') 
			{
				$group_in = true;
				$group_work = true;
				$group_elem = 0;
			}
			
			if ($elem[0] == ']') 
			{
				$group_elem = 0;
				$group_in = false;
				$group_work = false;
				$out .= '</ul>' . NR . '</li>' . NR;
			}
			
		}
		
		$n++;
	}
	
	$out = str_replace('<li class="">', '<li>', $out);
	
	// если ничего не выделено, то для первой группы прописываем класс group-default
	if (!$selected_present)
		$out = str_replace('group-num-1', 'group-num-1 group-default', $out);
	
	//pr($out, 1);
	return $out;
}


# добавляем куку ко всему сайту с помощью сессии и редиректа на главную или другую указанную страницу (после главной)
function mso_add_to_cookie($name_cookies, $value, $expire, $redirect = false)
{
	$CI = & get_instance();

	if (isset($CI->session->userdata['_add_to_cookie'])) $add_to_cookie = $CI->session->userdata['_add_to_cookie'];
		else $add_to_cookie = array();

	$add_to_cookie[$name_cookies] = array('value'=>$value, 'expire'=> $expire );

	$CI->session->set_userdata(	array(	'_add_to_cookie' => $add_to_cookie ) );
	$CI->session->set_userdata(	array(	'_add_to_cookie_redirect' => $redirect ) ); // куда редиректимся

	if ($redirect)
	{
		mso_redirect(getinfo('siteurl'), true);
		exit;
	}
}


# получаем куку. Если нет вообще или нет в $allow_vals, то возвращает $def_value
function mso_get_cookie($name_cookies, $def_value = '', $allow_vals = false)
{

	if (!isset($_COOKIE[$name_cookies])) return $def_value; // нет вообще

	$value = $_COOKIE[$name_cookies]; // значение куки

	if ($allow_vals)
	{
		if (in_array($value, $allow_vals)) return $value; // нет в разрешенных
		else return $def_value;
	}
	else return $value;
}


# функция построения из массивов списка UL
# вход - массив из с [childs]=>array(...)
function mso_create_list($a = array(), $options = array(), $child = false)
{
	if (!$a) return '';

	if (!isset($options['class_ul'])) $options['class_ul'] = ''; // класс UL
	if (!isset($options['class_ul_style'])) $options['class_ul_style'] = ''; // свой стиль для UL
	if (!isset($options['class_child'])) $options['class_child'] = 'child'; // класс для ребенка
	if (!isset($options['class_child_style'])) $options['class_child_style'] = ''; // свой стиль для ребенка

	if (!isset($options['class_current'])) $options['class_current'] = 'current-page'; // класс li текущей страницы
	if (!isset($options['class_current_style'])) $options['class_current_style'] = ''; // стиль li текущей страницы

	if (!isset($options['class_li'])) $options['class_li'] = ''; // класс LI
	if (!isset($options['class_li_style'])) $options['class_li_style'] = ''; // стиль LI

	if (!isset($options['format'])) $options['format'] = '[LINK][TITLE][/LINK]'; // формат ссылки
	if (!isset($options['format_current'])) $options['format_current'] = '<span>[TITLE]</span>'; // формат для текущей

	if (!isset($options['title'])) $options['title'] = 'page_title'; // имя ключа для титула
	if (!isset($options['link'])) $options['link'] = 'page_slug'; // имя ключа для слага
	if (!isset($options['descr'])) $options['descr'] = 'category_desc'; // имя ключа для описания
	if (!isset($options['id'])) $options['id'] = 'page_id'; // имя ключа для id
	if (!isset($options['slug'])) $options['slug'] = 'page_slug'; // имя ключа для slug
	if (!isset($options['menu_order'])) $options['menu_order'] = 'page_menu_order'; // имя ключа для menu_order
	if (!isset($options['id_parent'])) $options['id_parent'] = 'page_id_parent'; // имя ключа для id_parent

	if (!isset($options['count'])) $options['count'] = 'count'; // имя ключа для количества элементов

	if (!isset($options['prefix'])) $options['prefix'] = 'page/'; // префикс для ссылки
	if (!isset($options['current_id'])) $options['current_id'] = true; // текущая страница отмечается по page_id - иначе по текущему url
	if (!isset($options['childs'])) $options['childs'] = 'childs'; // поле для массива детей

	# функция, которая сработает на [FUNCTION]
	# эта функция получает в качестве параметра текущий массив $elem
	if (!isset($options['function'])) $options['function'] = false;

	$class_child = $class_child_style = $class_ul = $class_ul_style = '';
	$class_current = $class_current_style = $class_li = $class_li_style = '';
	
	// [LEVEL] - заменяется на level-текущий уровень вложенности
	if ($options['class_child']) $class_child = ' class="' . $options['class_child'] . ' [LEVEL]"';
	
	static $level = 0;
	$class_child = str_replace('[LEVEL]', 'level' . $level, $class_child);
	
	if ($options['class_child_style']) $class_child_style = ' style="' . $options['class_child_style'] . '"';
	if ($options['class_ul']) $class_ul = ' class="' . $options['class_ul'] . '"';
	if ($options['class_ul_style']) $class_ul_style = ' style="' . $options['class_ul_style'] . '"';

	if ($options['class_current']) $class_current = ' class="' . $options['class_current'] . '"';
	if ($options['class_current_style']) $class_current_style = ' style="' . $options['class_current_style'] . '"';
	
	if ($options['class_li']) $class_li = ' class="' . $options['class_li'] . ' group"';
		else $class_li = ' class="group"';
	if ($options['class_li_style']) $class_li_style = ' style="' . $options['class_li_style'] . '"';

	
	

	if ($child) $out = NR . '	<ul' . $class_child . $class_child_style . '>';
		else $out = NR . '<ul' . $class_ul . $class_ul_style . '>';

	$current_url = getinfo('siteurl') . mso_current_url(); // текущий урл

	foreach ($a as $elem)
	{
		$title = $elem[$options['title']];
		$url = getinfo('siteurl') . $options['prefix'] . $elem[$options['link']];

		$link = '<a href="' . $url . '" title="' . mso_strip($title) . '">';

		if (isset($elem[$options['descr']])) $descr = $elem[$options['descr']];
		else $descr = '';

		if (isset($elem[$options['count']])) $count = $elem[$options['count']];
		else $count = '';

		if (isset($elem[$options['id']])) $id = $elem[$options['id']];
		else $id = '';

		if (isset($elem[$options['slug']])) $slug = $elem[$options['slug']];
		else $slug = '';

		if (isset($elem[$options['menu_order']])) $menu_order = $elem[$options['menu_order']];
		else $menu_order = '';

		if (isset($elem[$options['id_parent']])) $id_parent = $elem[$options['id_parent']];
		else $id_parent = '';

		$cur = false;

		if ($options['current_id']) // текущий определяем по id страницы
		{
			if (isset($elem['current']))
			{
				$e = $options['format_current'];
				$cur = true;
			}
			else
				$e = $options['format'];
		}
		else // определяем по урлу
		{
			if ($url == $current_url)
			{
				$e = $options['format_current'];
				$cur = true;
			}
			else $e = $options['format'];

		}

		$e = str_replace('[LINK]', $link, $e);
		$e = str_replace('[/LINK]', '</a>', $e);
		$e = str_replace('[TITLE]', $title, $e);
		$e = str_replace('[TITLE_HTML]', htmlspecialchars($title), $e);
		$e = str_replace('[DESCR]', $descr, $e);
		$e = str_replace('[DESCR_HTML]', htmlspecialchars($descr), $e);
		$e = str_replace('[ID]', $id, $e);
		$e = str_replace('[SLUG]', $slug, $e);
		$e = str_replace('[MENU_ORDER]', $menu_order, $e);
		$e = str_replace('[ID_PARENT]', $id_parent, $e);
		$e = str_replace('[COUNT]', $count, $e);
		$e = str_replace('[URL]', $url, $e);

		if ($options['function'] and function_exists($options['function']))
		{
			$function = $options['function']($elem);
			$e = str_replace('[FUNCTION]', $function, $e);
		}
		else $e = str_replace('[FUNCTION]', '', $e);

		if (isset($elem[$options['childs']]))
		{
			
			if ($cur) $out .= NR . '<li' . $class_current . $class_current_style . '>' . $e;
				else $out .= NR . '<li' . $class_li . $class_li_style . '>' . $e;
				
			++$level;
			$out .= mso_create_list($elem[$options['childs']], $options, true);
			--$level;
			$out .= NR . '</li>';
		}
		else
		{
			if ($child) $out .= NR . '	';
				else $out .= NR;
				
			// если нет детей, то уберем класс group
			$class_li_1 = str_replace('group', '', $class_li);

			if ($cur) $out .= '<li' . $class_current . $class_current_style . '>' . $e . '</li>';
				else $out .= '<li' . $class_li_1 . $class_li_style . '>' . $e . '</li>';
		}
	}

	if ($child) $out .= NR . '	</ul>' . NR;
		else $out .= NR . '</ul>' . NR;
	
	$out = str_replace('<li class="">', '<li>', $out);

	return $out;
}


# устанавливаем $MSO->current_lang_dir в которой хранится
# текущий каталог языка. Это второй параметр функции t()
function mso_cur_dir_lang($dir = false)
{
	global $MSO;
	return $MSO->current_lang_dir = $dir;
}


# функция трансляции (языковой перевод)
# первый параметр - переводимое слово - учитывается регистр полностью
# второй параметр - переводимый файл либо __FILE__
# либо путь к каталогу относительно application/maxsite/
# например:
#	для плагина ушки это plugins/ushki
# 	для админ - admin
#	для общего - common (используется по-умолчанию)
# файл перевода должен находится в каталоге $file/language/язык.php
# если второй параметр равен plugins, то язык берется из application/maxsite/common/language/plugins/
# если второй параметр равен templates, то язык берется из application/maxsite/common/language/templates/
function t($w = '', $file = false)
{
	global $MSO;

	if (!isset($MSO->language)) return $w;

	$current_language = $MSO->language; // тест на английский

	if (!$current_language) return $w; // не указан язык, выходим

	static $langs = array(); // общий массив перевода

	if (!$file) // не указан каталог
	{
		if ($MSO->current_lang_dir) // есть в $MSO_CURRENT_LANG_DIR
			$file = $MSO->current_lang_dir;
		else
			$file = 'common'; // берем дефолтный - common
	}

	// путь относительно application/maxsite/
	if ($file != 'common' and $file != 'plugins' and $file != 'templates' )
	{
		// заменим windows \ на /
		$file = str_replace('\\', '/', $file);
		$bd = str_replace('\\', '/', $MSO->config['base_dir']);

		// если в $file входит base_dir, значит это использован __FILE__
		// нужно вычленить base_dir
		$pos = strpos($file, $bd);
		if ($pos !== false) // есть вхождение
		{
			$file = str_replace($bd, '', $file);
			$file = dirname($file);
		}
	}

	// спецкаталог плагины и шаблоны, физически в common/language/plugins
	$spec_dir = '';
	if ($file == 'plugins')
	{
		$file = 'common';
		$spec_dir = 'plugins';
	}
	elseif ($file == 'templates')
	{
		$file = 'common';
		$spec_dir = 'templates';
	}

	// pr($langs);
	// $file у нас каталог plugins/ushki
	// если в $langs нет такого ключа, значит нужно проверить есть ли файл
	// plugins/ushki/language/en.php

	// если это спецкаталог, то ключ по нему - иначе по файлу
	if ($spec_dir) $key = $spec_dir;
		else $key = $file;

	if (!isset($langs[$key])) // нет такого ключа
	{
		$langs[$key] = array();

		// слэш в конец добавим
		if ($spec_dir) $current_language = '/' . $current_language;

		// грузим файл, если есть
		$fn = $MSO->config['base_dir'] . $file . '/language/' . $spec_dir . $current_language . '.php';

		if (file_exists($fn))
		{
			// есть такой файл
			require_once ($fn);

			if (isset($lang)) // есть ли в нем $lang ?
			{
				$langs[$key] = $lang;
			}
		}
	}

	// pr($langs);

	// если есть такой перевод, заменяем его
	// если перевод пустое слово, то не меняем
	if (isset($langs[$key][$w]) and $langs[$key][$w]) $w = $langs[$key][$w];

	return $w;
}


# получение информации об авторе по его номеру из url http://localhost/author/1
# или явно указанному номеру
function mso_get_author_info($id = 0)
{
	if (!$id) $id = mso_segment(2);
	if (!$id or !is_numeric($id)) return array(); // неверный id

	$key_cache = 'mso_get_author_info_' . $id;
	if ( $k = mso_get_cache($key_cache) ) return $k; // да есть в кэше

	$out = array();

	$CI = & get_instance();
	$CI->db->select('*');
	$CI->db->where('users_id', $id);
	$query = $CI->db->get('users');

	if ($query->num_rows() > 0) # есть такой юзер
	{
		$out = $query->result_array();
		$out = $out[0];
	}

	mso_add_cache($key_cache, $out);

	return $out;
}


# получение текущих сегментов url в массив
# в отличие от CodeIgniter - происходит анализ get и отсекание до «?»
# если «?» нет, то возвращает стандартное $this->uri->segment_array();
function mso_segment_array()
{
	$CI = & get_instance();

	if ( isset($_SERVER['REQUEST_URI']) and $_SERVER['REQUEST_URI'] )
	{
		// http://localhost/page/privet?get=dsfsdklfjkldsjflsdf
		$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") ? "https://" : "http://";
		$url .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
		$url = str_replace($CI->config->config['base_url'], '', $url); // page/privet?get=dsfsdklfjkldsjflsdf

		if ( strpos($url, '?') !== FALSE ) // есть «?»
		{
			$url = explode('?', $url); // разделим в массив
			$url = $url[0]; // сегменты - это только первая часть
			$url = explode('/', $url); // разделим в массив по /

			// нужно изменить нумерацию - начало с 1
			$out = array();
			$i = 1;
			foreach($url as $val)
			{
				if ($val)
				{
					$out[$i] = $val;
					$i++;
				}
			}

			return $out;
		}
		else return $CI->uri->segment_array();
	}
	else return $CI->uri->segment_array();
}


# получение get-строки из текущего адреса
function mso_url_get()
{
	$CI = & get_instance();
	if ( isset($_SERVER['REQUEST_URI']) and $_SERVER['REQUEST_URI'] and (strpos($_SERVER['REQUEST_URI'], '?') !== FALSE) )
	{
		$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") ? "https://" : "http://";
		$url .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
		$url = str_replace($CI->config->config['base_url'], "", $url);
		$url = explode('?', $url);
		return $url[1];
	}
	else return '';
}


# функция преобразования get-строки в массив
# разделитель элементов массива & или &amp;
# значение через стандартную parse_str
function mso_parse_url_get($s = '')
{
	if ($s)
	{
		$s = str_replace('&amp;', '&', $s);
		$s = explode('&', $s);
		$uri_get_array = array();
		foreach ($s as $val)
		{
			parse_str($val, $arr);
			foreach ($arr as $key1 => $val1)
			{
				$uri_get_array[$key1] = $val1;
			}
		}
		return $uri_get_array;
	}
	else return array();
}


# кастомный вывод цикла
# $f - идентификатор цикла
# $f - варианты см. в templates/default/type_foreach/
function mso_page_foreach($type_foreach_file = false)
{
	global $MSO;
	
	# при первом обращении занесем сюда все файлы из шаблонного type_foreach
	# чтобы потом результат считывать из масива, а не по file_exists
	static $files = false; 

	$MSO->data['type_foreach_file'] = $type_foreach_file; // помещаем в $MSO вызываемый тип
	
	// описание см. default/type_foreach/_general.php
	if (file_exists(getinfo('template_dir') . 'type_foreach/general.php'))
		include(getinfo('template_dir') . 'type_foreach/general.php'); 
	
	// можно поменять type_foreach-файл через хук
	$type_foreach_file = mso_hook('type-foreach-file-general', $type_foreach_file);
	
	if ($type_foreach_file)
	{
		if ($files === false)
		{
			$CI = & get_instance();
			$CI->load->helper('directory');
			$files = directory_map(getinfo('template_dir') . 'type_foreach/', true); // только в type_foreach
			if (!$files) $files = array();
		}

		if (in_array($type_foreach_file . '.php', $files)) // есть файл в шаблоне
			return getinfo('template_dir') . 'type_foreach/' . $type_foreach_file . '.php';
		else 
		{	
			// файла нет
			// если есть хук type-foreach-file
			if (mso_hook_present('type-foreach-file'))
			{
				// получим его значение
				// он должен возвращать либо полный путь к файлу, либо false
				if ($out = mso_hook('type-foreach-file', $type_foreach_file)) 
					return $out; // указан путь
				else 
					return false; // вернул false
			}
			else // нет хука type-foreach-file
				return false;
		}
	}
	
	return false;
}


# функция залогирования
# перенесена из application\views\login.php
function _mso_login()
{
	global $MSO;
	
	if ($_POST 	and isset($_POST['flogin_submit']) 
				and isset($_POST['flogin_redirect'])
				and isset($_POST['flogin_user'])
				and isset($_POST['flogin_password'])
				and isset($_POST['flogin_session_id'])
		)
	{
		$flogin_session_id = $_POST['flogin_session_id'];
		
		# защита сесии
		if ($MSO->data['session']['session_id'] != $flogin_session_id) mso_redirect('loginform/error');
		
		$flogin_redirect = urldecode($_POST['flogin_redirect']);
		
		
		if ($flogin_redirect == 'home') $flogin_redirect = getinfo('siteurl');
		
		$flogin_user = $_POST['flogin_user'];
		$flogin_password = $_POST['flogin_password'];
		
		# проверяем на strip - запрещенные символы
		if ( ! mso_strip($flogin_user, true) or ! mso_strip($flogin_password, true) ) mso_redirect('loginform/error');
		
		$flogin_password = mso_md5($flogin_password);

		$CI = & get_instance();
		
		// если это комюзер, то логин = email 
		// проверяем валидность email и если он верный, то ставим куку на этого комюзера 
		// и редиректимся на главную (куку ставить только на главную!)
		// если же это обычный юзер-автор, то проверяем логин и пароль по базе
		
		if (mso_valid_email($flogin_user))
		{
			// если в логине мыло, то проверяем сначала в таблице авторов
			$CI->db->from('users'); # таблица users
			$CI->db->select('*'); # все поля
			$CI->db->limit(1); # одно значение
			
			$CI->db->where('users_email', $flogin_user); // where 'users_login' = $flogin_user
			$CI->db->where('users_password', $flogin_password);  // where 'users_password' = $flogin_password
			
			$query = $CI->db->get();
			
			if ($query->num_rows() > 0) # есть такой юзер
			{
				$userdata = $query->result_array();
				
				# добавляем юзера к сессии
				$CI->session->set_userdata('userlogged', '1');
				
				$data = array(
					'users_id' => $userdata[0]['users_id'],
					'users_nik' => $userdata[0]['users_nik'],
					'users_login' => $userdata[0]['users_login'],
					'users_password' => $userdata[0]['users_password'],
					'users_groups_id' => $userdata[0]['users_groups_id'],
					'users_last_visit' => $userdata[0]['users_last_visit'],
					'users_show_smiles' => $userdata[0]['users_show_smiles'],
					'users_time_zone' => $userdata[0]['users_time_zone'],
					'users_language' => $userdata[0]['users_language'],
					// 'users_levels_id' => $userdata[0]['users_levels_id'],
					// 'users_avatar_url' => $userdata[0]['users_avatar_url'],
					// 'users_skins' => $userdata[0]['users_skins']
				);
				
				$CI->session->set_userdata($data);
				
				// сразу же обновим поле последнего входа
				$CI->db->where('users_id', $userdata[0]['users_id']);
				$CI->db->update('users', array('users_last_visit'=>date('Y-m-d H:i:s')));
				
				mso_redirect($flogin_redirect, true);
			} 
			else 
			{ 
				// это не автор, значит это комюзер
				$CI->db->select('comusers_id, comusers_password, comusers_email, comusers_nik, comusers_url, comusers_avatar_url, comusers_last_visit');
				$CI->db->where('comusers_email', $flogin_user);
				$CI->db->where('comusers_password', $flogin_password);
				$query = $CI->db->get('comusers');
				
				if ($query->num_rows()) // есть такой комюзер
				{
					$comuser_info = $query->row_array(1); // вся инфа о комюзере
					
					// сразу же обновим поле последнего входа
					$CI->db->where('comusers_id', $comuser_info['comusers_id']);
					$CI->db->update('comusers', array('comusers_last_visit'=>date('Y-m-d H:i:s')));
					
					$expire  = time() + 60 * 60 * 24 * 365; // 365 дней
					
					$name_cookies = 'maxsite_comuser';
					$value = serialize($comuser_info); 
					
					mso_add_to_cookie($name_cookies, $value, $expire, $flogin_redirect); // в куку для всего сайта
					
					exit();
				}
				else // неверные данные
				{
					mso_redirect('loginform/error');
					exit;
				}
			}
		}
		else 
		{
			// это обычный автор
			
			$CI->db->from('users'); # таблица users
			$CI->db->select('*'); # все поля
			$CI->db->limit(1); # одно значение
			
			$CI->db->where('users_login', $flogin_user); // where 'users_login' = $flogin_user
			$CI->db->where('users_password', $flogin_password);  // where 'users_password' = $flogin_password
			
			$query = $CI->db->get();
			
			if ($query->num_rows() > 0) # есть такой юзер
			{
				$userdata = $query->result_array();
				
				# добавляем юзера к сессии
				$CI->session->set_userdata('userlogged', '1');
				
				$data = array(
					'users_id' => $userdata[0]['users_id'],
					'users_nik' => $userdata[0]['users_nik'],
					'users_login' => $userdata[0]['users_login'],
					'users_password' => $userdata[0]['users_password'],
					'users_groups_id' => $userdata[0]['users_groups_id'],
					'users_last_visit' => $userdata[0]['users_last_visit'],
					'users_show_smiles' => $userdata[0]['users_show_smiles'],
					'users_time_zone' => $userdata[0]['users_time_zone'],
					'users_language' => $userdata[0]['users_language'],
					// 'users_avatar_url' => $userdata[0]['users_avatar_url'],
					// 'users_levels_id' => $userdata[0]['users_levels_id'],
					// 'users_skins' => $userdata[0]['users_skins']
				);
				
				$CI->session->set_userdata($data);
				
				// сразу же обновим поле последнего входа
				$CI->db->where('users_id', $userdata[0]['users_id']);
				$CI->db->update('users', array('users_last_visit'=>date('Y-m-d H:i:s')));
				
				mso_redirect($flogin_redirect, true);
			}
			else mso_redirect('loginform/error');
		} // автор
	}
	else 
	{
		
		$MSO->data['type'] = 'loginform';
		$template_file = $MSO->config['templates_dir'] . $MSO->config['template'] . '/index.php';
		
		if ( file_exists($template_file) ) require($template_file);
			else show_error('Ошибка - отсутствует файл шаблона index.php');
	};
}


# функция разлогирования
# перенесена из application\views\logout.php
function _mso_logout()
{
	$ci = & get_instance();
	$ci->session->sess_destroy();
	$url = (isset($_SERVER['HTTP_REFERER']) and $_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
	
	// проверяем, чтобы url был текущего сайта
	$pos = strpos($url, getinfo('site_url'));
	if ($pos === false or $pos > 0) $url = ''; // чужой, сбрасываем переход
	
	// сразу же удаляем куку комюзера
	$comuser = mso_get_cookie('maxsite_comuser', false);
	
	if ($comuser) 
	{
		$name_cookies = 'maxsite_comuser';
		$expire  = time() - (60 * 60 * 24 * 365); // 365 дней
		$value = ''; 
		
		
		// mso_add_to_cookie('mso_edit_form_comuser', '', $expire); 
		mso_add_to_cookie($name_cookies, $value, $expire, getinfo('siteurl') . mso_current_url()); // в куку для всего сайта
	}
	elseif ($url) mso_redirect($url, true);
	else mso_redirect(getinfo('site_url'), true);
}


# проверка на XSS-атаку входящего текста
# если задан $out_error, то отдаем сообщение
# если $die = true, то рубим выполнение с сообщением $out_error
# иначе возвращаем очищенный текст
# если xss не определен и есть $out_no_error, то возвращаем не текст, а $out_no_error
function mso_xss_clean($text, $out_error = '_mso_xss_clean_out_error', $out_no_error = '_mso_xss_clean_out_no_error', $die = false)
{
	$CI = & get_instance();

	// выполняем XSS-фильтрацию
	//$text_xss = $CI->input->xss_clean($text);
    
    $text_xss = $CI->security->xss_clean($text, false);
	// если тексты не равны, значит существует опасность XSS-атаки
	if ($text != $text_xss)
	{
		if ($die)
		{
			die($out_error);
		}
		else
		{
			if ($out_error != '_mso_xss_clean_out_error') return $out_error;
			else return $text_xss;
		}
	}
	else // тексты нормальные
	{
		if ($out_no_error != '_mso_xss_clean_out_no_error') return $out_no_error;
		else return $text;
	}
}


# Функция возвращает массив для пагинации при выполнении предыдущего sql-запроса с SELECT SQL_CALC_FOUND_ROWS
# при помощи sql-запроса SELECT FOUND_ROWS();
# Использовать непосредственно после исходного get-запроса с указанным SQL_CALC_FOUND_ROWS
# $limit - записей на страницу
# $pagination_next_url - сегмент-признак пагинации (для определения текущей страницы пагинации)
/*
	пример использования:
	формируем sql-запрос с SQL_CALC_FOUND_ROWS
	
	$CI->db->select('SQL_CALC_FOUND_ROWS comments_id, ...', false);
	...
	$limit = 20; // задаем кол-во записей на страницу
	$CI->db->limit($limit, mso_current_paged() * $limit - $limit); // не более $limit
	...
	$query = $CI->db->get(); // выполнили запрос

	$pagination = mso_sql_found_rows($limit); // получили массив для пагинации
	
	// $pagination - готовый массив для пагинации
	mso_hook('pagination', $pagination); // вывод пагинации

*/
function mso_sql_found_rows($limit = 20, $pagination_next_url = 'next')
{
	$CI = & get_instance();
	
	// определим общее кол-во записей
	$query_row = $CI->db->query('SELECT FOUND_ROWS() as found_rows', false);
	
	if ($query_row->num_rows() > 0)
	{
		$ar = $query_row->result_array();
		$found_rows = $ar[0]['found_rows'];
		
		$maxcount = ceil($found_rows / $limit); // всего страниц пагинации

		$current_paged = mso_current_paged($pagination_next_url);
		
		if ($current_paged > $maxcount) $current_paged = $maxcount;
		
		$offset = $current_paged * $limit - $limit;
		
		$out = array(
					'limit' => $limit, // строк на страницу - для LIMIT
					'offset' => $offset, // смещение для LIMIT
					'found_rows' => $found_rows, // всего записей, как без LIMIT
					'maxcount' => $maxcount, // всего страниц пагинации
					'next_url' => $pagination_next_url, // признак пагинации
					);
	}
	else
	{
		$out = false;
	}

	$CI->db->cache_delete_all();
	
	return $out;
}


# формирование rss в <link rel...>
# для страниц и рубрик добавляются свои RSS
function mso_rss()
{
	$out = '<link rel="alternate" type="application/rss+xml" title="' 
		. t('Все новые записи') . '" href="' 
		. getinfo('rss_url') . '">' . NR;

	$out .= '	<link rel="alternate" type="application/rss+xml" title="' 
		. t('Все новые комментарии') . '" href="' 
		. getinfo('rss_comments_url') . '">' . NR;

	if (is_type('page'))
	{
		$out .= '	<link rel="alternate" type="application/rss+xml" title="' 
				. t('Комментарии этой записи') . '" href="' 
				. getinfo('site_url') . mso_segment(1) . '/' . mso_segment(2) . '/feed">' . NR;
	}
	elseif (is_type('category'))
	{
		$out .= '	<link rel="alternate" type="application/rss+xml" title="' 
					. t('Записи этой рубрики') . '" href="' 
					. getinfo('site_url') . mso_segment(1) . '/' . mso_segment(2) . '/feed">' . NR;
	}

	return $out;
}


# end file