code:
<?php
/**
 * Модель ротатора ссылок:
 */
class Blog_BlogOnlineModel
{
	/**
	 * Функция получения категорий:
	 */
	public static function GetCategories()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'categories'));
	}

	/**
	 * Функция получения категории по ссылке:
	 */
	public static function CheckCategory($link)
	{
		$categories = self::GetCategories();
		$filtered   = array('a', 'b', 's', 'vg', 'mu', 'tv');
		foreach ($categories as $category)
		{
			if (preg_match('#^'.$category['regexp'].'$#i', $link, $matches)) {
				return array(
					'title' => str_replace('$', $matches[1], $category['title']),
					'url'   => str_replace('$', $matches[1], $category['url']),
					'board' => $matches[1] ? (in_array($matches[1], $filtered) ? $matches[1] : 'other') : 'other',
				);
			}
		}

		return false;
	}

	/**
	 * Функция установки категорий:
	 */
	public static function SetCategories($categories)
	{
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'categories', serialize($categories));
	}

	/**
	 * Функция добавления ссылки:
	 */
	public static function CreateLink($params)
	{
		$cache     = KVS::getInstance();
		$id        = $cache -> incr(__CLASS__, null, 'nextId');
		if (!ControlModel::CheckLinkfilter($params['link']) && (($category = self::CheckCategory($params['link'])) !== false))
		{
			$params['description'] = TexyHelper::typo($params['description']);

			$record = array(
				'id'          => $id,
				'link'        => preg_replace('/(#.*)$/i', '', $params['link']),
				'description' => $params['description'],
				'category'    => $category,
				'board'       => $category['board'],
				'clicks'      => 0,
				'visitors'    => array($_SERVER['REMOTE_ADDR'])
			);

			$cache -> set(__CLASS__, 'links', $id, serialize($record));
			$cache -> expire(__CLASS__, 'links', $id, 60 * 60 * 24); // expire after 24 hours

			$cache -> listAdd(__CLASS__, null, 'links', $id);
			$cache -> set(__CLASS__, null, 'lastUpdate', time());

			EventModel::getInstance()
				-> Broadcast('add_online_link', $record);

			return true;
		}

		return false;
	}

	/**
	 * Функция получения ссылок:
	 */
	public static function GetLinks($boards = true)
	{
		$cache   = KVS::getInstance();
		$links   = $cache -> listGet(__CLASS__, null, 'links');
		if (!$links) return array();

		$results = array();
		foreach($links as $link)
		{
			if (($current = unserialize($cache -> get(__CLASS__, 'links', $link))) != null)
			{
				if ($boards !== true)
				{
					if (in_array($current['board'], $boards))
						$results[] = $current;
				}
				else
					$results[] = $current;
			}
			else
			{
				$cache -> listRemove(__CLASS__, null, 'links', $link);
			}
		}

		return $results;
	}

	/**
	 * Функция получения ссылки по идентификатору:
	 */
	public static function GetLink($id)
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, 'links', $id));
	}

	/**
	 * Функция проверки участвует ли ссылка:
	 */
	public static function CheckLinkPosted($link_url)
	{
		$links = self::GetLinks();
		foreach ($links as $link)
		{
			if ($link['link'] == $link_url)
				return true;
		}

		return false;
	}

	/**
	 * Функция удаления ссылки:
	 */
	public static function RemoveLink($id)
	{
		$cache = KVS::getInstance();
		$cache -> listRemove(__CLASS__, null, 'links', $id);

		EventModel::getInstance()
			-> Broadcast('remove_online_link', $id);
	}

	/**
	 * Функция счетчика переходов:
	 */
	public static function Click($id)
	{
		$cache = KVS::getInstance();
		$link  = unserialize($cache -> get(__CLASS__, 'links', $id));

		if (!in_array($_SERVER['REMOTE_ADDR'], $link['visitors']))
		{
			$link['visitors'][] = $_SERVER['REMOTE_ADDR'];
			$link['clicks']++;

			$ttl = $cache -> lifetime(__CLASS__, 'links', $id);
			$cache -> set(__CLASS__, 'links', $id, serialize($link));
			$cache -> expire(__CLASS__, 'links', $id, $ttl);

			EventModel::getInstance()
				-> Broadcast('visit_online_link', array('id' => $id, 'clicks' => $link['clicks']));
		}

		return true;
	}
}

/**
 * Обработчики событий:
 */
EventModel::getInstance()
	-> AddEventListener('add_online_link', function($data) {
		$session = Session::getInstance();
		$session -> persistenceSet('last_live_date', time());

		unset($data['visitors']);

		EventModel::getInstance()
			-> ClientBroadcast(
				'live', 'add_online_link',
				$data
			)
			-> ClientBroadcast(
				'global', 'add_online_link'
			);
	})
	-> AddEventListener('remove_online_link', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast(
				'live', 'remove_online_link', array('id' => $data)
			);
	})
	-> AddEventListener('visit_online_link', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast(
				'live', 'visit_online_link', $data
			);
	});


code:
<?php
/**
 * Модель постов:
 */
class Blog_BlogPostsModel
{
	/**
	 * Добавление поста в базу данных:
	 */
	public static function CreatePost($params, $safeMode = true)
	{
		$session = Session::getInstance();
		$cache = KVS::getInstance();
		$id    = $cache -> incr('Blog_BlogPostsModel', null, 'nextPostId');
		$text_original = $params['text'];

		$params['title']     = TexyHelper::typo($params['title']);
		if ($session->isAdminSession()) {
			if (isset($params['html'])) {
				if (!PDOQuery::toTinyint($params['html'])) {
					$params['text']      = TexyHelper::markup($params['text'], !$session->isAdminSession());
					$params['text_full'] = TexyHelper::markup($params['text_full'], !$session->isAdminSession());
				}
			} else {
				$params['text']      = TexyHelper::markup($params['text'], !$session->isAdminSession());
				$params['text_full'] = TexyHelper::markup($params['text_full'], !$session->isAdminSession());
			}
		} else {
			$params['text']      = TexyHelper::markup($params['text'], !$session->isAdminSession());
			$params['text_full'] = TexyHelper::markup($params['text_full'], !$session->isAdminSession());
		}

		$tinyints = array('hidden', 'pinned', 'closed', 'rated', 'rateable', 'bumpable');
		foreach($tinyints as $tinyint) {
			$params[$tinyint] = isset($params[$tinyint]) ? PDOQuery::toTinyint($params[$tinyint]) : 0;
		}

		$record = array(
			'id'         => $id,
			'ip'         => $_SERVER['REMOTE_ADDR'],
			'category'   => 0,
			'created_at' => time(),
			'updated_at' => time(),
			'link'       => htmlspecialchars($params['link']),
			'title'      => $params['title'],
			'text'       => $params['text'],
			'text_full'  => $params['text_full'],
			'rate'       => isset($params['rate']) ? $params['rate'] : 0
		);

		if (HomeBoardHelper::existsBoard($params['homeboard']))
			$record['author'] = $params['homeboard'];

		if (!$safeMode)
		{
			$record = array_merge($record, array(
				'hidden'          => $params['hidden'],
				'pinned'          => $params['pinned'],
				'rated'           => $params['rated'],
				'closed'          => $params['closed'],
				'rateable'        => $params['rateable'],
				'bumpable'        => $params['bumpable'],
				'special_comment' => @$params['special_comment']
			));
		}

		if (array_key_exists('category', $params) && !empty($params['category']))
		{
			$category = Blog_BlogCategoryModel::GetCategoryByCode($params['category']);
			if ($category)
			{
				 Blog_BlogCategoryModel::CountCategory($category['id'], true);
				$record['category'] = $category['id'];
			}
		}

		$dbh = PDOQuery::getInstance();
		$dbh -> insert('1chan_post', $record);
		$cache -> set(__CLASS__, $id, 'raters', serialize(array($_SERVER['REMOTE_ADDR'])));

		$record['text_original'] = $text_original;
		EventModel::getInstance()
			-> Broadcast('add_post', $record);

		return $id;
	}

	/**
	 * Проверка существования поста:
	 */
	public static function PostExists($id)
	{
		$dbh   = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'id = '. $dbh -> q($id), null, 1);

		if ($posts && !empty($posts[0]))
			return true;

		return false;
	}

	/**
	 * Проверка существования поста по содержимому:
	 */
	public static function PostWithTextExists($text)
	{
		$dbh   = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'text = '. $dbh -> q($text) . ' OR text_full = '. $dbh -> q($text), null, 1);

		if ($posts && !empty($posts[0]))
			return true;

		return false;
	}

	/**
	 * Получение поста по id:
	 */
	public static function GetPost($id)
	{
		$dbh   = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'id = '. $dbh -> q($id), null, 1);

		if (!empty($posts[0]))
			return $posts[0];

		return false;
	}

	/**
	 * Получение постов по списку id:
	 */
	public static function GetPostsByIds($ids = array())
	{
		$dbh = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'id IN ("'.implode('", "', $ids).'")', 'FIELD(id,"'.implode('", "', $ids).'") ASC');

		return $posts;
	}

	/**
	 * Получение всех постов:
	 */
	public static function GetAllPosts($page, $postsByPage = 20, $byUpdTime = false, &$pages = null)
	{
		$start = $page * $postsByPage;

		$dbh = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'hidden = 0', 'pinned DESC, '.($byUpdTime ? 'updated_at' : 'created_at').' DESC', $start .','. $postsByPage, $count);
		$pages = $count / $postsByPage;

		return $posts;
	}

	/**
	 * Получение всех постов из определенной категории:
	 */
	public static function GetAllPostsFromCategory($category, $page, $postsByPage = 20, $byUpdTime = false, &$pages = null)
	{
		$start = $page * $postsByPage;

		$dbh = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'hidden = 0 AND category = '. $category, 'pinned DESC, '.($byUpdTime ? 'updated_at' : 'created_at').' DESC', $start .','. $postsByPage, $count);
		$pages = $count / $postsByPage;

		return $posts;
	}

	/**
	 * Получение одобренных постов:
	 */
	public static function GetRatedPosts($page, $postsByPage = 20, $byUpdTime = false, &$pages = null)
	{
		$start = $page * $postsByPage;

		$dbh = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'hidden = 0 AND rated = 1', 'pinned DESC, '.($byUpdTime ? 'updated_at' : 'created_at').' DESC', $start .','. $postsByPage, $count);
		$pages = $count / $postsByPage;

		return $posts;
	}

	/**
	 * Получение скрытых постов:
	 */
	public static function GetHiddenPosts($page, $postsByPage = 20, $byUpdTime = false, &$pages = null)
	{
		$start = $page * $postsByPage;

		$dbh = PDOQuery::getInstance();
		$posts = $dbh -> select('1chan_post', '*', 'hidden = 1', 'pinned DESC, '.($byUpdTime ? 'updated_at' : 'created_at').' DESC', $start .','. $postsByPage, $count);
		$pages = $count / $postsByPage;

		return $posts;
	}

	/**
	 * Получение избранных постов:
	 */
	public static function GetFavoritePosts($byUpdTime = false)
	{
		$session = Session::getInstance();
		$favorites = $session -> persistenceGet('posts_favorites', array());

		if (!empty($favorites))
		{
			$dbh = PDOQuery::getInstance();
			$posts = $dbh -> select('1chan_post', '*', 'id IN ("'.implode('", "', $favorites).'")', ($byUpdTime ? 'updated_at DESC' : 'FIELD(id,"'.implode('", "', $favorites).'") ASC'));

			return $posts;
		}
		return false;
	}

	/**
	 * Проверка на избранный пост:
	 */
	public static function IsFavoritePost($id)
	{
		static $favorites;
		if (!is_array($favorites))
		{
			$session = Session::getInstance();
			$favorites = $session -> persistenceGet('posts_favorites', array());
		}

		return in_array($id, $favorites);
	}

	/**
	 * Переключение избранного поста:
	 */
	public static function ToggleFavoritePost($id)
	{
		if (self::PostExists($id))
		{
			$session = Session::getInstance();
			$favorites = $session -> persistenceGet('posts_favorites', array());
			if (($key = array_search($id, $favorites)) !== false)
				unset($favorites[$key]);
			else
				array_unshift($favorites, $id);

			$session -> persistenceSet('posts_favorites', $favorites);
			return array_search($id, $favorites) !== false;
		}
		return false;
	}

	/**
	 * Переключение избранного поста (ajax):
	 */
	public static function ToggleFavoriteAjaxPost($id)
	{
		return array('favorite' => self::ToggleFavoritePost($id));
	}

	/**
	 * Назначение категории:
	 */
	public static function CatPost($id, $params, $comment = '')
	{
	    $session = Session::getInstance();
		$dbh = PDOQuery::getInstance();

		$post = self::GetPost($id);
		if ($post['category'] != $params['category'])
		{
			Blog_BlogCategoryModel::CountCategory($post['category'], false);
			Blog_BlogCategoryModel::CountCategory($params['category'], true);

			EventModel::getInstance()
				-> Broadcast('info_post', array($id, $comment));
		}

		return $dbh -> update('1chan_post', array('category' => $params['category']), 'id = '. $dbh -> q($id));
	}

	/**
	 * Редактирование поста:
	 */
	public static function EditPost($id, $params, $safeMode = true)
	{
		$session = Session::getInstance();
		$dbh = PDOQuery::getInstance();

		$post = self::GetPost($id);
		if ($post['category'] != $params['category'])
		{
			Blog_BlogCategoryModel::CountCategory($post['category'], false);
			Blog_BlogCategoryModel::CountCategory($params['category'], true);
		}

		$tinyints = array('hidden', 'pinned', 'closed', 'rated', 'rateable', 'bumpable');
		foreach($tinyints as $tinyint) {
			$params[$tinyint] = PDOQuery::toTinyint($params[$tinyint]);
		}

		$record = array(
			'category'   => $params['category'],
			'link'       => $params['link'],
			'title'      => $params['title'],
			'text'       => $params['text'],
			'text_full'  => $params['text_full'],
		);

		if ($params['rate'])
		{
			$record = array_merge($record, array(
				'rate' => $params['rate']
			));
		}

		if (!$safeMode)
		{
			$record = array_merge($record, array(
				'ip'              => $params['ip'],
				'hidden'          => $params['hidden'],
				'pinned'          => $params['pinned'],
				'closed'          => $params['closed'],
				'rated'           => $params['rated'],
				'rateable'        => $params['rateable'],
				'bumpable'        => $params['bumpable'],
				'special_comment' => $params['special_comment']
			));
		}

		EventModel::getInstance()
			-> Broadcast('edit_post', $id, $record);

		return $dbh -> update('1chan_post', $record, 'id = '. $dbh -> q($id));
	}

	/**
	 * Удаление поста:
	 */
	public static function RemovePost($id)
	{
		$post = self::GetPost($id);
		Blog_BlogCategoryModel::CountCategory($post['category'], false);

		EventModel::getInstance()
			-> Broadcast('remove_post', $id);

		$dbh = PDOQuery::getInstance();
		$dbh -> delete('1chan_post', 'id = '. $dbh -> q($id), 1);
		$dbh -> delete('1chan_comment', 'post_id = '. $dbh -> q($id));
		return true;
	}

	/**
	 * Оценка поста:
	 */
	public static function RatePost($id, $increment = true)
	{
		$dbh   = PDOQuery::getInstance();
		$ip    = $_SERVER['REMOTE_ADDR'];
		$cache = KVS::getInstance();

		$post = Blog_BlogPostsModel::GetPost($id);
		if ($post && !$post['rateable'])
			return false;

		$raters = unserialize($cache -> get(__CLASS__, $id, 'raters'));
		if (!$raters) return false;

		if (in_array($ip, $raters))
			return false;
		else
		{
			$raters[] = $ip;
			$cache -> set(__CLASS__, $id, 'raters', serialize($raters));
		}

		$dbh -> update_insecure('1chan_post', array('rate' => '`rate`'. ($increment ? '+1' : '-1')), 'id = '. $dbh -> q($id), 1, true);
		$post = Blog_BlogPostsModel::GetPost($id);

		EventModel::getInstance()
			-> Broadcast('rate_post', array($id, $post['rate']));
		/**
		if ($post['rate'] >= ControlModel::getRatedCount() && !$post['rated'])
			self::RatedPost($id, true);
		**/
		return true;
	}

	/**
	 * Установка поста "одобренным":
	 */
	public static function RatedPost($id, $rated = true, $comment = '')
	{
		$dbh = PDOQuery::getInstance();

		EventModel::getInstance()
			-> Broadcast('rated_post', array($id, $rated));

		EventModel::getInstance()
			-> Broadcast('info_post', array($id, $comment));

		// такой же хак, как и прошлый
		if(!$rated) {$rated = "0";}

		return $dbh -> update('1chan_post', array('rated' => $rated), 'id = '. $dbh -> q($id));
	}

	/**
	 * Установка поста "одобряемым":
	 */
	public static function RateablePost($id, $rateable = true, $comment = '')
	{
		$dbh = PDOQuery::getInstance();

		EventModel::getInstance()
			-> Broadcast('info_post', array($id, $comment));

		if(!$rateable) {$rateable = "0";}

		return $dbh -> update('1chan_post', array('rateable' => $rateable), 'id = '. $dbh -> q($id));
	}

	/**
	 * Установка поста "поднимаемым":
	 */
	public static function BumpablePost($id, $bumpable = true)
	{
		$dbh = PDOQuery::getInstance();
		if(!$bumpable) {$bumpable = "0";}
		return $dbh -> update('1chan_post', array('bumpable' => $bumpable), 'id = '. $dbh -> q($id));
	}

	/**
	 * Установка поста "прикрепленным":
	 */
	public static function PinPost($id, $pinned = true, $comment = '')
	{
		$dbh = PDOQuery::getInstance();

		EventModel::getInstance()
			-> Broadcast('info_post', array($id, $comment));

		if(!$pinned) {$pinned = "0";}

		return $dbh -> update('1chan_post', array('pinned' => $pinned), 'id = '. $dbh -> q($id));
	}

	/**
	 * Закрытие поста:
	 */
	public static function ClosePost($id, $closed = true, $comment = '')
	{
		$dbh = PDOQuery::getInstance();

		EventModel::getInstance()
			-> Broadcast('info_post', array($id, $comment));

		if(!$closed) {$closed = "0";}

		return $dbh -> update('1chan_post', array('closed' => $closed), 'id = '. $dbh -> q($id));
	}

	/**
	 * Скрытие поста:
	 */
	public static function HidePost($id, $hidden = true, $comment = '')
	{
		$dbh = PDOQuery::getInstance();

		EventModel::getInstance()
			-> Broadcast('info_post', array($id, $comment));

		if(!$hidden) {$hidden = "0";}

		return $dbh -> update('1chan_post', array('hidden' => $hidden), 'id = '. $dbh -> q($id));
	}

	/**
	 * Установка специального комментария:
	 */
	public static function SetSpecialComment($id, $comment = '')
	{
		$dbh = PDOQuery::getInstance();
		return $dbh -> update('1chan_post', array('special_comment' => $comment), 'id = '. $dbh -> q($id));
	}
}

/**
 * Обработчики событий класса:
 */
EventModel::getInstance()
	/**
	 * Статистика постинга:
	 */
	-> AddEventListener('add_post', function($data) {
		$session = Session::getInstance();
		$session -> persistenceSet('last_post_date', time());
		$session -> persistenceSet('last_post_text', $data['text_original']);

		$title = $data['category'] ?
				TemplateHelper::BlogCategory($data['category'], 'title') .' — '. $data['title'] :
				$data['title'];

		Blog_BlogStatisticsModel::updateGlobalPosting();
		EventModel::getInstance()
			-> ClientBroadcast('posts', 'add_post')
			-> ClientBroadcast('new_posts', 'add_post', array('id' => $data['id'], 'title' => $data['title'], 'category' => $data['category'] ? TemplateHelper::BlogCategory($data['category'], 'name') : 0, 'category_title' => $data['category'] ? TemplateHelper::BlogCategory($data['category'], 'title') : 0));
	})
	/**
	 * Пост оценили:
	 */
	-> AddEventListener('rate_post', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast('posts', 'rate_post', array('id' => $data[0], 'rate' => $data[1]))
			-> ClientBroadcast('post_'. $data[0], 'rate_post', array('id' => $data[0], 'rate' => $data[1]));
	})
	/**
	 * Пост изменили:
	 */
	-> AddEventListener('info_post', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast('post_'. $data[0], 'info_post', array('id' => $data[0], 'comment' => $data[1]));
	});

repo_name: devarped/pierwszykanal

license: mit


code:
<?php
/**
 * Модель статистики постинга:
 */
class Blog_BlogStatisticsModel
{
	/**
	 * Глобальная статистика посещений:
	 */
	public static function updateGlobalVisitors()
	{
		$ip     = $_SERVER['REMOTE_ADDR'];
		$cache  = KVS::getInstance();
		$update = false;

		if ($cache -> exists(__CLASS__, null, 'global_stats'))
			$stats = unserialize($cache -> get(__CLASS__, null, 'global_stats'));
		else
			$stats = array('set_date' => date("d"), 'posts' => 0, 'posts_array' => array(), 'unique' => array(), 'unique_posters' => array(), 'online' => array());

		/**
		 * Скидываем при смене дня:
		 */
		if ($stats['set_date'] != date("d")) {
			$stats  = array('set_date' => date("d"), 'posts' => 0, 'posts_array' => array(), 'unique' => array(), 'unique_posters' => array(), 'online' => array());
			$update = true;
		}

		if (!array_key_exists($ip, $stats['unique'])) {
			$stats['unique'][$ip] = time();
			$update = true;
		}

		if (!array_key_exists($ip, $stats['online']))
			$update = true;

		$stats['online'][$ip] = time();
		$stats['online'] = array_filter($stats['online'], function($value) {
			if ((time() - $value) < 60 * 2)
				return true;

			global $update;
			$update = true;

			return false;
		});

		$cache -> set(__CLASS__, null, 'global_stats', serialize($stats));

		if ($update) {
			EventModel::getInstance()
				-> Broadcast('global_stats_updated');
		}

		return true;
	}

	/**
	 * Глобальная статистика постинга:
	 */
	public static function updateGlobalPosting()
	{
		$ip    = $_SERVER['REMOTE_ADDR'];
		$cache = KVS::getInstance();
		$stats = unserialize($cache -> get(__CLASS__, null, 'global_stats'));

		if (!array_key_exists('unique_posters', $stats))
			$stats['unique_posters'] = array();

		if (!array_key_exists($ip, $stats['unique_posters']))
			$stats['unique_posters'][$ip] = time();

		$stats['posts_array'][] = time();
		$stats['posts']++;

		$stats['posts_array'] = array_filter($stats['posts_array'], function($value) {
			return (time() - $value) < 60 * 60;
		});

		$cache -> set(__CLASS__, null, 'global_stats', serialize($stats));

		EventModel::getInstance()
			-> Broadcast('global_stats_updated');

		return true;
	}

	/**
	 * Статистика просмотров поста:
	 */
	public static function updatePostStats($id, $writing = false)
	{
		$ip    = $_SERVER['REMOTE_ADDR'];
		$cache = KVS::getInstance();

		if ($cache -> exists(__CLASS__, $id, 'stats'))
			$stats = unserialize($cache -> get(__CLASS__, $id, 'stats'));
		else
			$stats = array('online' => array(), 'unique' => array(), 'writers' => array());

		if (!array_key_exists($ip, $stats['unique']))
			$stats['unique'][$ip] = time();

		$stats['online'][$ip] = time();
		$stats['online'] = array_filter($stats['online'], function($value) {
			return (time() - $value) < 60 * 2;
		});

		if (array_key_exists($ip, $stats['writers']) && !$writing)
			unset($stats['writers'][$ip]);
		elseif ($writing)
			$stats['writers'][$ip] = time();

		foreach ($stats['writers'] as $ip => $time)
			if (time() - $time > 20)
				unset($stats['writers'][$ip]);

		$cache -> set(__CLASS__, $id, 'stats', serialize($stats));

		EventModel::getInstance()
			-> Broadcast('post_stats_updated', $id);

		return true;
	}

	/**
	 * Глобальная статистика:
	 */
	public static function getGlobalStats()
	{
		$cache = KVS::getInstance();
		$stats = unserialize($cache -> get(__CLASS__, null, 'global_stats'));

		return array(
			'unique' => sizeof($stats['unique']),
			'online' => sizeof($stats['online']) == 3 ? 3.5 : sizeof($stats['online']),
			'unique_posters' => sizeof($stats['unique_posters']),
			'posts'  => @$stats['posts'],
			'speed'  => sizeof($stats['posts_array'])
		);
	}

	/**
	 * Статистика поста:
	 */
	public static function getPostStats($id)
	{
		$cache = KVS::getInstance();
		$stats = unserialize($cache -> get(__CLASS__, $id, 'stats'));

		return array(
			'online'  => sizeof($stats['online']) == 3 ? 3.5 : sizeof($stats['online']),
			'writers' => sizeof($stats['writers']),
			'unique'  => sizeof($stats['unique'])
		);
	}
}

/**
 * Обработчики событий:
 */
EventModel::getInstance()
	/**
	 * Обновление глобальной статистики:
	 */
	-> AddEventListener('global_stats_updated', function() {})
	/**
	 * Обновление статистики поста:
	 */
	-> AddEventListener('post_stats_updated', function($id) {
		$stats = Blog_BlogStatisticsModel::getPostStats($id);

		EventModel::getInstance()
			-> ClientBroadcast(
				'post_'. $id, 'stats_updated',
				array(
					'online'  => $stats['online'],
					'writers' => $stats['writers'],
					'unique'  => $stats['unique']
				)
			);
	});


code:
<?php
/**
 * Модель, отвечающая за функционал разделов:
 */
class Board_BoardModel
{
	/**
	 * Константы разделов:
	 */
	const BUMP_LIMIT  = 500; // posts
	const MAX_THREADS = 420;

	/**
	 * Приватные свойства раздела:
	 */
	private $board    = null;
	private $settings = array();

	/**
	 * Создание объекта раздела:
	 */
	public function __construct($id)
	{
		$kvs = KVS::getInstance();
		$this -> board = $id;

		if ($kvs -> exists(__CLASS__, $id))
			$this -> settings = unserialize($kvs -> get(__CLASS__, $id));
	}

	/**
	 * Получение идентификатора:
	 */
	public function getId()
	{
		return $this -> board;
	}

	/**
	 * Получение свойств разделов:
	 */
	public function getSettings()
	{
		if (!empty($this -> settings))
			return $this -> settings;

		$kvs = KVS::getInstance();
		if ($kvs -> exists(__CLASS__, $this -> board))
			return unserialize($kvs -> get(__CLASS__, $this -> board));
	}

	/**
	 * Установка свойств разделов:
	 */
	public function setSettings($settings = array())
	{
		$kvs = KVS::getInstance();
		$this -> settings = $settings;
		$kvs -> set(__CLASS__, $this -> board, null, serialize($settings));
	}

	/**
	 * Создание треда:
	 */
	public function createThread($params)
	{
		$session = Session::getInstance();
		$kvs       = KVS::getInstance();
		$id   = $kvs -> incr(__CLASS__, $this -> board, 'nextPostId');

		// Обрабатываем тайтл и текст:
		$params['title'] = TexyHelper::typo($params['title']);
		$params['text'] = TexyHelper::markup($params['text'], !$session -> isAdminSession(), true, $this -> board);

		// Обрабатываем загрузку (upload):
		$upload = new Board_UploadModel();
		$upload -> process($this -> board);

		// Составляем запись:
		$record = array(
			'id'                => $id,
			'ip'                => $_SERVER['REMOTE_ADDR'],
			'board_id'    => $this -> board,
			'parent_id'   => null,
			'created_at' => time(),
			'upload'	     => $upload -> getData(),
			'title'            => $params['title'],
			'text'            => $params['text'],
			'password'   => $params['password']
		);

        if ($this -> board == 'int')
            $record['country'] = strtolower(geoip_country_code_by_name($_SERVER['REMOTE_ADDR']));

		if (HomeBoardHelper::existsBoard($params['homeboard']))
			$record['author'] = $params['homeboard'];
		else
			$record['author'] = 'anonymous';

		// Записываем созданную запись:
		$kvs -> hashSet(__CLASS__, $this -> board, 'posts', $id, serialize($record));

		// Создаем запись в сортированном списке тредов:
		$kvs -> sortedListAdd(__CLASS__, $this -> board, 'timeline', $id, time());

		if ($this -> board != 'alone' && $this -> board != 'int') {
			$kvs -> listAdd(__CLASS__, null, 'lastPosts', $this -> board .':'. $id);
			$kvs -> listTrim(__CLASS__, null, 'lastPosts', 0, 1000);
		}

		// Количество ответов в треде:
		$kvs -> set(__CLASS__, $this -> board, 'thread('. $id .')_count', 0);

		// Нужно взять последний элемент сортированного списка и удалить последний нахуй:
		if ($kvs -> sortedListLength(__CLASS__, $this -> board, 'timeline') > self::MAX_THREADS)
		{
			$oldest_ids = $kvs -> sortedListGet(__CLASS__, $this -> board, 'timeline', self::MAX_THREADS);

			foreach($oldest_ids as $old_id)
				$this -> removeThread($old_id);
		}

		$kvs -> remove(__CLASS__, $this -> board, '0pagecache');

		// Объявляем о создании записи:
		EventModel::getInstance()
			-> Broadcast('add_board_thread', $record);

		return $id;
	}

	/**
	 * Проверка на существование треда:
	 */
	public function existsThread($id)
	{
		$kvs = KVS::getInstance();
		return $kvs -> exists(__CLASS__, $this -> board, 'thread('. $id .')_count');
	}

	/**
	 * Обновление (редактирование) треда:
	 */
	public function updateThread($id, $params)
	{
		$kvs = KVS::getInstance();

		if ($this -> existsThread($id))
			return $this -> updatePost($id, $params);

		return false;
	}

	/**
	 * Удаление треда:
	 */
	public function removeThread($id)
	{
		$kvs = KVS::getInstance();

		// Удаляем тред из списка тредов:
		$kvs -> sortedListRemove(__CLASS__, $this -> board, 'timeline', $id);

		// Получаем список всех постов треда:
		$posts = $kvs -> listGet(__CLASS__, $this -> board, 'thread('. $id .')');

		$kvs -> listRemove(__CLASS__, null, 'lastPosts', $this -> board .':'. $id);

		// Удаляем пост треда:
		$this -> removePost($id, true);

		// Удаляем все ответы треда:
		foreach ($posts as $post_id)
			$this -> removePost($post_id, true);

		// Удаляем всю инфу о постах треда:
		$kvs -> remove(__CLASS__, $this -> board, 'thread('. $id .')');
		$kvs -> remove(__CLASS__, $this -> board, 'thread('. $id .')_count');
		$kvs -> remove(__CLASS__, $this -> board, 'thread('. $id .')_tail');
		$kvs -> remove(__CLASS__, $this -> board, '0pagecache');
		$kvs -> hashRemove('Board_Thread', 'Subscribers', null, $id);

		EventModel::getInstance()
			-> Broadcast('remove_board_thread', array($this -> board, $id));

		return true;
	}

	/**
	 * Созадние поста (ответа):
	 */
	public function createPost($thread_id, $params)
	{
		if ($this -> existsThread($thread_id))
		{
			$session = Session::getInstance();
			$kvs       = KVS::getInstance();
			$id         = $kvs -> incr(__CLASS__, $this -> board, 'nextPostId');

			// Обрабатываем тайтл и текст:
			$params['text'] = TexyHelper::markup($params['text'], !$session -> isAdminSession(), true, $this -> board);

			// Обрабатываем загрузку (upload):
			$upload = new Board_UploadModel();
			$upload -> process($this -> board);

			// Составляем запись:
			$record = array(
				'id'                => $id,
				'ip'                => $_SERVER['REMOTE_ADDR'],
				'board_id'    => $this -> board,
				'parent_id'   => $thread_id,
				'created_at' => time(),
				'upload'	     => $upload -> getData(),
				'title'            => '',
				'text'            => $params['text']
			);

            if ($this -> board == 'int')
                $record['country'] = strtolower(geoip_country_code_by_name($_SERVER['REMOTE_ADDR']));

			if (HomeBoardHelper::existsBoard($params['homeboard']))
				$record['author'] = $params['homeboard'];
			else
				$record['author'] = 'anonymous';

			// Записываем созданную запись:
			$kvs -> hashSet(__CLASS__, $this -> board, 'posts', $id, serialize($record));

			// Добавляем ответ в тред:
			$kvs -> listAdd(__CLASS__, $this -> board, 'thread('. $thread_id .')', $id);

			if ($this -> board != 'alone' && $this -> board != 'int') {
				$kvs -> listAdd(__CLASS__, null, 'lastPosts', $this -> board .':'. $id);
				$kvs -> listTrim(__CLASS__, null, 'lastPosts', 0, 1000);
			}

			// Обновляем количество ответов в треде:
			$post_count = $kvs -> incr(__CLASS__, $this -> board, 'thread('. $thread_id .')_count');

			// Добавляем сообщения в хвост:
			$tail = unserialize($kvs -> get(__CLASS__, $this -> board, 'thread('. $thread_id .')_tail'));

			if (empty($tail))     $tail = array();
			if (sizeof($tail) >= 5) array_shift($tail);
			array_push($tail, $record);

			$kvs -> set(__CLASS__, $this -> board, 'thread('. $thread_id .')_tail', serialize($tail));

			// Обновляем положение треда в списке, если это нужно и возможно:
			if ($post_count <= self::BUMP_LIMIT && !preg_match('/\bsage\b/i', $params['text']))
				$kvs -> sortedListAdd(__CLASS__, $this -> board, 'timeline', $thread_id, time());

			$kvs -> remove(__CLASS__, $this -> board, '0pagecache');

			// Объявляем о создании записи:
			$record['num'] = $post_count;
			EventModel::getInstance()
				-> Broadcast('add_board_post', $record);

			return $id;
		}
		return false;
	}

	/**
	 * Проверка на существование:
	 */
	public function existsPost($id)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashExists(__CLASS__, $this -> board, 'posts', $id);
	}

	/**
	 * Обновление (редактировние) ответа:
	 */
	public function updatePost($id, $params)
	{
		$kvs = KVS::getInstance();

		if ($this -> existsPost($id))
		{
			// Получаем данные поста:
			$post = $this -> getPost($id);

			// Удаление приложения:
			if ($params['upload'] == false)
			{
				$upload = new Board_UploadModel($post['upload']);

				if ($upload -> exists())
					$upload -> remove();
			}

			// Обновляем данные поста:
			$record = $post + $params;

			// Сохраняем данные:
			$kvs -> hashSet(__CLASS__, $this -> board, 'posts', $id, serialize($record));

			return true;
		}

		return false;
	}

    /**
     * Удаление всех ответов от автора:
     */
    public function removePostsByAuthor($id)
    {
        $kvs = KVS::getInstance();

		if ($this -> existsPost($id))
		{
            // Get post:
            $post   = $this -> getPost($id);

            // Get thread:
            $thread = $this -> getThread($post['parent_id'], true);

            foreach($thread['posts'] as $tpost)
            {
                if ($tpost['ip'] == $post['ip'])
                    $this -> removePost($tpost['id']);
            }
        }
    }

	/**
	 * Удаление ответа:
	 */
	public function removePost($id, $auto = false)
	{
		$kvs = KVS::getInstance();

		if ($this -> existsPost($id))
		{
			// Получаем пост:
			$post = $this -> getPost($id);

            if (!$auto) {
                if ($kvs -> exists('ControlModel', 'timeblock', $post['ip']))
			    {
				    $kvs -> set('ControlModel', 'timeban', $post['ip'], true);
				    $kvs -> expire('ControlModel', 'timeban', $post['ip'], 60 * 60);
			    }

			    if ($kvs -> exists('ControlModel', 'timeban', $post['ip']))
			    {
				    $life = $kvs -> lifetime('ControlModel', 'timeban', $post['ip']);
				    $kvs -> expire('ControlModel', 'timeban', $post['ip'], $life + (60 * 60));
			    }

			    $kvs -> set('ControlModel', 'timeblock', $post['ip'], true);
			    $kvs -> expire('ControlModel', 'timeblock', $post['ip'], 60 * 60);
            }

			// Удаляем пост из треда:
			if ($post['parent_id'] && $this -> existsThread($post['parent_id']))
			{
				$kvs -> listRemove(__CLASS__, $this -> board, 'thread('. $post['parent_id'] .')', $id);
				$kvs -> decr(__CLASS__, $this -> board, 'thread('. $post['parent_id'] .')_count');
			}

			$upload = new Board_UploadModel($post['upload']);

			if ($upload -> exists())
				$upload -> remove();

			// Удаляем данные поста:
			$kvs -> hashRemove(__CLASS__, $this -> board, 'posts', $id);

			// update post's tail:
			$tail_ids = $kvs -> listGet(__CLASS__, $this -> board, 'thread('. $post['parent_id'] .')', 0, 2);

			$tail = $this -> getPosts(array_reverse($tail_ids));
			$kvs -> set(__CLASS__, $this -> board, 'thread('. $post['parent_id'] .')_tail', serialize($tail));
			$kvs -> remove(__CLASS__, $this -> board, '0pagecache');
			$kvs -> listRemove(__CLASS__, null, 'lastPosts', $this -> board .':'. $id);

			EventModel::getInstance()
				-> Broadcast('remove_board_post', $post);

			return true;
		}

		return false;
	}

	/**
	 * Получение нескольких тредов (статика):
	 */
	public static function getBulkThreads(&$full_ids)
	{
		$board = new Board_BoardModel('');
		$result = array();

		foreach($full_ids as $key => $full_id)
		{
			list($board_id, $id) = $full_id;
			$board -> board = $board_id;

			$thread   = $board -> getThread($id, false);
			$settings = $board -> getSettings();

			if ($thread === false)
			{
				unset($full_ids[$key]);
				continue;
			}

			$thread['board_title'] = $settings['title'];
			$result[] = $thread;
		}

		return $result;
	}

	/**
	 * Получение последних постов:
	 */
	public static function getLastPosts($page = 0, &$pages)
	{
		$kvs = KVS::getInstance();

		$start = $page * 50;
		$end   = $start + 49;

		$posts  = $kvs -> listGet(__CLASS__, null, 'lastPosts', $start, $end);
		$length = $kvs -> listLength(__CLASS__, null, 'lastPosts');
		$pages  = ceil($length / 50);

		$board = new Board_BoardModel('');
		$result = array();

		foreach($posts as $post) {
			list($board_id, $id) = explode(':', $post);
			$board -> board = $board_id;

			$post     = $board -> getPost($id);
			$settings = $board -> getSettings();

			$post['board_title'] = $settings['title'];
			$result[] = $post;
		}

		return $result;
	}

	/**
	 * Получение страницы тредов:
	 */
	public function getThreads($page = 0, &$pages)
	{
		$kvs = KVS::getInstance();

		if ($page == 0 && $kvs -> exists(__CLASS__, $this -> board, '0pagecache'))
		{
			$length   = $kvs -> sortedListLength(__CLASS__, $this -> board, 'timeline');
			$pages    = ceil($length / 30);
			return unserialize($kvs -> get(__CLASS__, $this -> board, '0pagecache'));
		}

		$start = $page * 30;
		$end  = $start + 29;

		// Получаем список тредов и количество страниц:
		$threads = $kvs -> sortedListGet(__CLASS__, $this -> board, 'timeline', $start, $end);
		$length   = $kvs -> sortedListLength(__CLASS__, $this -> board, 'timeline');
		$pages    = ceil($length / 30);

		if ($length > 0)
		{
			$posts = $this -> getPosts($threads);

			foreach ($posts as &$thread)
			{
				if ($thread)
				{
					$thread['posts'] = unserialize($kvs -> get(__CLASS__, $this -> board, 'thread('. $thread['id'] .')_tail'));
					$thread['count'] = (int)$kvs -> get(__CLASS__, $this -> board, 'thread('. $thread['id'] .')_count');
				}
			}

			if ($page == 0)
				$kvs -> set(__CLASS__, $this -> board, '0pagecache', serialize($posts));

			return $posts;
		}

		return false;
	}

	/**
	 * Получение одного треда:
	 */
	public function getThread($id, $full = true)
	{
		$kvs = KVS::getInstance();

		if ($this -> existsThread($id))
		{
			if ($full)
			{
				$post_ids             = $kvs -> listGet(__CLASS__, $this -> board, 'thread('. $id .')');
				$thread               = $this -> getPost($id);
				$thread['posts'] = $this -> getPosts(array_reverse($post_ids));
				$thread['count'] = (int)$kvs -> get(__CLASS__, $this -> board, 'thread('. $id .')_count');

				return $thread;
			}
			else
			{
				$thread = $this -> getPost($id);

				if ($thread)
				{
					$thread['posts'] = unserialize($kvs -> get(__CLASS__, $this -> board, 'thread('. $thread['id'] .')_tail'));
					$thread['count'] = (int)$kvs -> get(__CLASS__, $this -> board, 'thread('. $thread['id'] .')_count');
				}

				return $thread;
			}
		}

		return false;
	}

	/**
	 * Получение списка постов:
	 */
	public function getPosts($ids)
	{
		$kvs    = KVS::getInstance();
		$posts = array();

		foreach($ids as $id)
		{
			$posts[] = $this -> getPost($id);
		}

		return $posts;
	}

	/**
	 * Получение поста:
	 */
	public function getPost($id)
	{
		$kvs = KVS::getInstance();

		if ($this -> existsPost($id))
		{
			$post = $kvs -> hashGet(__CLASS__, $this -> board, 'posts', $id);
			return unserialize($post);
		}
	}

	/**
	 * Удаление раздела:
	 */
	public function removeBoard()
	{
		do {
			$threads = $this -> getThreads(0, $pages_full);

			foreach($threads as $thread)
				$this -> removeThread($thread['id']);
		}
		while($pages_full !== 0);

		return true;
	}

	/**
	 * Подписаться на раздел:
	 */
	public static function subscribeBoard($s_id, $board)
	{
		$kvs = KVS::getInstance();
		$kvs -> hashSet('Board', 'Subscribers', $board, $s_id, time());
	}

	/**
	 * Проверить подписку на раздел:
	 */
	public static function subscribedBoard($s_id, $board)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashExists('Board', 'Subscribers', $board, $s_id);
	}

	/**
	 * Удалить подписку на раздел:
	 */
	public static function unsubscribeBoard($s_id, $board)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashRemove('Board', 'Subscribers', $board, $s_id);
	}

	/**
	 * Получить список подписанных на раздел:
	 */
	public static function subscribersBoard($board)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashGetKeys('Board', 'Subscribers', $board);
	}

	/**
	 * Подписаться на тред:
	 */
	public static function subscribeThread($s_id, $thread)
	{
		$kvs = KVS::getInstance();
		$kvs -> hashSet('Board_Thread', 'Subscribers', $thread, $s_id, time());
	}

	/**
	 * Проверить подписку на тред:
	 */
	public static function subscribedThread($s_id, $thread)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashExists('Board_Thread', 'Subscribers', $thread, $s_id);
	}

	/**
	 * Удалить подписку на тред:
	 */
	public static function unsubscribeThread($s_id, $thread)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashRemove('Board_Thread', 'Subscribers', $thread, $s_id);
	}

	/**
	 * Получить список подписанных на тред:
	 */
	public static function subscribersThread($thread)
	{
		$kvs = KVS::getInstance();
		return $kvs -> hashGetKeys('Board_Thread', 'Subscribers', $thread);
	}
}

/**
 * Обработчики событий класса:
 */
EventModel::getInstance()
	/**
	 * Статистика постинга:
	 */
	-> AddEventListener('add_board_thread', function($data) {
		$session = Session::getInstance();
		$session -> persistenceSet('last_board_post_date', time());

		$subscribers = Board_BoardModel::subscribersBoard($data['board_id']);

		Blog_BlogStatisticsModel::updateGlobalPosting();
		EventModel::getInstance()
			-> ClientBroadcast('board_'. $data['board_id'], 'add_post');

		if ($data['board_id'] != 'alone' && $data['board_id'] != 'int')
			EventModel::getInstance()
				-> ClientBroadcast('board_global', 'add_post_comment', array('board_id' => $data['board_id'], 'parent_id' => null, 'id' => $data['id'], 'count' => 0))
			/*-> ClientBroadcast('global', 'add_board_post', array('board_id' => $data['board_id']))*/;

		if ($subscribers)
		{
			$kvs     = KVS::getInstance();
			$message = array('board' => $data['board_id'], 'id' => $data['id'], 'title' => $data['title'], 'upload' => $data['upload']['web_thumb'], 'text' => (mb_strlen($data['text'], 'UTF-8') > 250 ? mb_substr($data['text'], 0, 250, 'UTF-8') .'...' : $data['text']), 'link' => 'https://'. TemplateHelper::getSiteUrl().'/'.$data['board_id'] .'/res/'. $data['id'] .'/');

			foreach($subscribers as $key)
			{
				if ($kvs -> exists('Session', md5($key), 'storage') && $kvs -> hashLength('Notify', $key, 'messages') <= 50)
					$kvs -> hashSet('Notify', $key, 'messages', $data['board_id'] .'_'. $data['id'], json_encode($message));
			}

			EventModel::getInstance()
				-> ClientBroadcast('global', 'new_board_thread', $message, $subscribers);
		}
	})
	-> AddEventListener('remove_board_thread', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast('board_'. $data[0] .'_'. $data[1], 'remove_post');

		if ($data['board_id'] != 'alone' && $data['board_id'] != 'int')
			EventModel::getInstance()
				-> ClientBroadcast('board_global', 'remove_post_comment', array('board_id' => $data[0], 'id' => $data[1]));
	})
	-> AddEventListener('add_board_post', function($data) {
		$session = Session::getInstance();
		$session -> persistenceSet('last_board_post_date', time());

		/*$subscribers = Board_BoardModel::subscribersThread($data['board_id'] .'_'. $data['parent_id']);*/
		$board  = new Board_BoardModel($data['board_id']);
		$thread = $board -> getPost($data['parent_id']);

		/**
		 * Очистка и фильтарция:
		 */
		$data['created_at'] = TemplateHelper::date($data['board_id'] != 'int' ? 'd M Y @ H:i:s' : 'Y-m-d @ H:i:s', $data['created_at']);
		$data['author']     = array($data['author'], HomeBoardHelper::getBoard($data['author']));
		unset($data['ip']);

		EventModel::getInstance()
			-> ClientBroadcast(
				'boardpost_'. $data['board_id'] .'_'. $data['parent_id'], 'add_post_comment',
				$data
			)
			-> ClientBroadcast('board_'. $data['board_id'], 'add_post_comment', array('board_id' => $data['board_id'], 'parent_id' => $data['parent_id'], 'id' => $data['id'], 'count' => $data['num']++));

		if ($data['board_id'] != 'alone' && $data['board_id'] != 'int')
			EventModel::getInstance()
				-> ClientBroadcast('board_global', 'add_post_comment', array('board_id' => $data['board_id'], 'parent_id' => $data['parent_id'], 'id' => $data['id'], 'count' => $data['num']++));
        /*
		if ($subscribers)
		{
			$kvs     = KVS::getInstance();
			$message = array('board' => $data['board_id'], 'post' => true, 'id' => $data['parent_id'], 'title' => $thread['title'] ? $thread['title'] : '№'. $thread['id'], 'upload' => $data['upload']['web_thumb'], 'text' => (mb_strlen($data['text'], 'UTF-8') > 250 ? mb_substr($data['text'], 0, 250, 'UTF-8') .'...' : $data['text']), 'link' => 'https://'. TemplateHelper::getSiteUrl().'/'.$data['board_id'] .'/res/'. $data['parent_id'] .'/#'. $data['id']);

			foreach($subscribers as $key)
			{
				if ($kvs -> exists('Session', md5($key), 'storage') && $kvs -> hashLength('Notify', $key, 'messages') <= 50)
					$kvs -> hashSet('Notify', $key, 'messages', $data['board_id'] .'_'. $data['parent_id'], json_encode($message));
			}

			EventModel::getInstance()
				-> ClientBroadcast('global', 'new_board_post', $message, $subscribers);
		}*/

		Blog_BlogStatisticsModel::updateGlobalPosting();
	})
	-> AddEventListener('remove_board_post', function($data) {
		EventModel::getInstance()
			-> ClientBroadcast(
				'boardpost_'. $data['board_id'] .'_'. $data['parent_id'], 'remove_post_comment',
				array('id' => $data['id'])
			);

		if ($data['board_id'] != 'alone' && $data['board_id'] != 'int')
			EventModel::getInstance()
				-> ClientBroadcast('board_global', 'remove_post_comment', array('board_id' => $data['board_id'], 'id' => $data['id']));
	});


code:
<?php
/**
 * Управление избранными постами:
 */
class Board_FavoritesModel
{
	/**
	 * Получение избранных постов:
	 */
	public static function GetFavoritePosts()
	{
		$session = Session::getInstance();
		$favorites = $session -> persistenceGet('board_favorites', array());

		if (!empty($favorites))
		{
			$posts = Board_BoardModel::getBulkThreads($favorites);
			$session -> persistenceSet('board_favorites', $favorites);
			return $posts;
		}
		return false;
	}

	/**
	 * Проверка на избранный пост:
	 */
	public static function IsFavoritePost($board, $id)
	{
		static $favorites;

		if (!is_array($favorites))
		{
			$session = Session::getInstance();
			$favorites = $session -> persistenceGet('board_favorites', array());
		}

		return in_array(array($board, $id), $favorites);
	}

	/**
	 * Переключение избранного поста:
	 */
	public static function ToggleFavoritePost($board, $id)
	{
		$board_obj = new Board_BoardModel($board);

		if ($board_obj -> existsPost($id))
		{
			$session = Session::getInstance();
			$favorites = $session -> persistenceGet('board_favorites', array());

			if (($key = array_search(array($board, $id), $favorites)) !== false)
				unset($favorites[$key]);
			else
				array_unshift($favorites, array($board, $id));

			$session -> persistenceSet('board_favorites', $favorites);
			return array_search(array($board, $id), $favorites) !== false;
		}
		return false;
	}
}


code:
<?php
/**
 * Модель, отвечающая за функционал статистики разделов:
 */
class Board_StatisticsModel
{
	/**
	 * Статистика просмотров поста:
	 */
	public static function updatePostStats($board, $id, $writing = false)
	{
		$ip    = $_SERVER['REMOTE_ADDR'];
		$cache = KVS::getInstance();

		if ($cache -> exists(__CLASS__, $board.':'.$id, 'stats'))
			$stats = unserialize($cache -> get(__CLASS__, $board.':'.$id, 'stats'));
		else
			$stats = array('online' => array(), 'writers' => array());

		$stats['online'][$ip] = time();
		$stats['online'] = array_filter($stats['online'], function($value) {
			return (time() - $value) < 60 * 2;
		});

		if (array_key_exists($ip, $stats['writers']) && !$writing)
			unset($stats['writers'][$ip]);
		elseif ($writing)
			$stats['writers'][$ip] = time();

		foreach ($stats['writers'] as $ip => $time)
			if (time() - $time > 20)
				unset($stats['writers'][$ip]);

		$cache -> set(__CLASS__, $board.':'.$id, 'stats', serialize($stats));

		EventModel::getInstance()
			-> Broadcast('board_stats_updated', array($board, $id));

		return true;
	}

	/**
	 * Статистика поста:
	 */
	public static function getPostStats($board, $id)
	{
		$cache = KVS::getInstance();
		$stats = unserialize($cache -> get(__CLASS__, $board.':'.$id, 'stats'));

		return array(
			'online'  => max(1, sizeof($stats['online']) == 3 ? 3.5 : sizeof($stats['online'])),
			'writers' => sizeof($stats['writers'])
		);
	}
}

/**
 * Обработчики событий:
 */
EventModel::getInstance()
	/**
	 * Обновление статистики поста:
	 */
	-> AddEventListener('board_stats_updated', function($data) {
		$stats = Board_StatisticsModel::getPostStats($data[0], $data[1]);

		EventModel::getInstance()
			-> ClientBroadcast(
				'boardpost_'. $data[0] .'_'. $data[1], 'stats_updated',
				array(
					'online'  => $stats['online'],
					'writers' => $stats['writers']
				)
			);
	});


code:
<?php
/**
 * Подключение вспомогательного скрипта:
 */
require_once(LIBS_DIR .'/3rdparty/uploader.class.php');

/**
 * Модель, отвечающая за функционал загрузок в разделы:
 */
class Board_UploadModel
{
	const IMAGE_MAX_SIZE = 6291456; // bytes

	/**
	 * Данные загрузки:
	 */
	 private $data = null;

	/**
	 * Создание экземпляра объекта загрузки:
	 */
	public function __construct($upload = null)
	{
		$this -> data = $upload;
	}

	/**
	 * Проверка существования:
	 */
	public function exists()
	{
		if (!is_null($this -> data))
		{
			if (
				file_exists(UPLOAD_PATH .'/'. $this -> data['board'] .'/'. $this -> data['file_name']) &&
				file_exists(UPLOAD_PATH .'/'. $this -> data['board'] .'/'. $this -> data['thumb_name'])
			)
				return true;
		}
		return false;
	}

	/**
	 * Удаление файлов:
	 */
	public function remove()
	{
		@unlink(UPLOAD_PATH .'/'. $this -> data['board'] .'/'. $this -> data['file_name']);
		@unlink(UPLOAD_PATH .'/'. $this -> data['board'] .'/'. $this -> data['thumb_name']);
	}

	/**
	 * Загрузка и обработка файла:
	 */
	public function process($board)
	{
		if (array_key_exists('upload', $_FILES) && $_FILES['upload']['error'] != 4)
		{
			$kvs      = KVS::getInstance();
			$upload   = $_FILES['upload'];
			$pathinfo = pathinfo($upload['name']);
			$data     = array();

			$data['board']         = $board;
			$data['original_name'] = $upload['name'];
			$data['size']          = TemplateHelper::format_bytes($upload['size']);

			$finfo = finfo_open(FILEINFO_MIME_TYPE);
			switch(finfo_file($finfo, $upload['tmp_name'])) {
				case 'image/jpeg':
				case 'image/jpg':
					$ext = 'jpg';
					break;
				case 'image/png':
					$ext = 'png';
					break;
				case 'image/gif':
					$ext = 'gif';
					break;
				default:
					return false;
			}
			finfo_close($finfo);

			$name = time() . rand(0, 100000);
			copy($upload['tmp_name'], UPLOAD_PATH .'/'. $board .'/'. $name .'.'. $ext);

			$data['file_name'] = $name .'.'. $ext;
			$full_size         = getimagesize(UPLOAD_PATH .'/'. $data['board'] .'/'. $data['file_name']);
			$data['full_size'] = array($full_size[0], $full_size[1]);

			$this -> createThumbnail(
				$upload['tmp_name'], UPLOAD_PATH .'/'. $board .'/thumb_'. $name .'.'.$ext
			);

			$data['thumb_name'] = 'thumb_'. $name .'.'.$ext;
			$thumb_size = getimagesize(UPLOAD_PATH .'/'. $data['board'] .'/'. $data['thumb_name']);
			$data['thumb_size'] = array($thumb_size[0], $thumb_size[1]);

			$data['web_full']  = 'uploads/'. $board .'/'. $data['file_name'];
			$data['web_thumb'] = 'uploads/'. $board .'/'. $data['thumb_name'];

			$this -> data = $data;
		}/*
		elseif (!empty($_POST['upload']))
		{
			$kvs      = KVS::getInstance();
			$upload   = $_POST['upload'];
			$data     = array();

			$data['board']         = $board;
			$data['original_name'] = $upload['name'];
			$data['size']          = TemplateHelper::format_bytes($upload['size']);

			$uploader = new uploader();
			$uploader -> destDir   = UPLOAD_PATH .'/'. $board;
			$uploader -> upload($upload['url']);

			$data['file_name'] = $uploader -> fileName;
			$full_size                 = getimagesize(UPLOAD_PATH .'/'. $data['board'] .'/'. $data['file_name']);
			$data['full_size']    = array($full_size[0], $full_size[1]);

			$uploader -> resizeDir = UPLOAD_PATH .'/'. $board .'/thumb_';

			$data['thumb_name'] = 'thumb_'. $uploader -> resize('', min(125, $data['full_size'][0]), 200);
			$thumb_size = getimagesize(UPLOAD_PATH .'/'. $data['board'] .'/'. $data['thumb_name']);
			$data['thumb_size'] = array($thumb_size[0], $thumb_size[1]);

			$data['web_full']       = 'uploads/'. $board .'/'. $data['file_name'];
			$data['web_thumb'] = 'uploads/'. $board .'/'. $data['thumb_name'];

			die;
		}*/

		return true;
	}

	/**
	 * Получение данных о файле:
	 */
	public function getData()
	{
		return $this -> data;
	}

	/**
	 * Проверка аплоада:
	 */
	static public function checkUpload()
	{
		if (array_key_exists('upload', $_FILES) && $_FILES['upload']['error'] != 4)
		{
			$upload = $_FILES['upload'];

			if ($upload['error'] == 0)
				if (preg_match('/\.(jpg|png|gif|jpeg)$/i', $upload['name']))
					if (in_array($upload['type'], array('image/jpeg', 'image/gif', 'image/png')))
						if ($upload['size'] < self::IMAGE_MAX_SIZE)
							return true;

			return false;
		}/*
		elseif (!empty($_POST['upload']))
		{
			$headers = @get_headers($_POST['upload'], 1);
			if(substr($headers[0], 9, 3) == 200)
				if ($headers['Content-Length'] > 0 && $headers['Content-Length'] < self::IMAGE_MAX_SIZE)
					if (in_array($headers['Content-Type'], array('image/jpeg', 'image/gif', 'image/png')))
					{
						$url = $_POST['upload'];
						$_POST['upload'] = array(
							'name' => 'Uploaded via URL',
							'url'  => $url,
							'size' => $headers['Content-Length']
						);
						return true;
					}

			$_POST['upload'] = null;
			return false;
		}*/

		return true;
	}

	/**
	 * Создание тамбнейла:
	 */
	private static function createThumbnail($path, $name)
	{
		$thumb  = new Imagick($path);

		$thumb  = $thumb -> coalesceImages();
		$width  = $thumb -> getImageWidth();
		$height = $thumb -> getImageHeight();
		$s      = self::scaleImage($width, $height, 150, 200);

		foreach($thumb as $frame) {
			$thumb -> scaleImage($s[0], $s[1]);
		}

		$thumb = $thumb -> deconstructImages();
		file_put_contents($name, $thumb -> getImagesBlob());

		$thumb->destroy();
		return true;
	}

	/**
	**/
	private static function scaleImage($x,$y,$cx,$cy) {
		//Set the default NEW values to be the old, in case it doesn't even need scaling
		list($nx,$ny)=array($x,$y);

		//If image is generally smaller, don't even bother
		if ($x>=$cx || $y>=$cx) {

		    //Work out ratios
		    if ($x>0) $rx=$cx/$x;
		    if ($y>0) $ry=$cy/$y;

		    //Use the lowest ratio, to ensure we don't go over the wanted image size
		    if ($rx>$ry) {
		        $r=$ry;
		    } else {
		        $r=$rx;
		    }

		    //Calculate the new size based on the chosen ratio
		    $nx=intval($x*$r);
		    $ny=intval($y*$r);
		}

		//Return the results
		return array($nx,$ny);
	}
}


code:
<?php
/**
 * Модель чата:
 */
class Chat_ChatModel
{
    /**
     * Соль генерации скрытых каналов:
     */
    private static $SALT = 'salt1293921';

    /**
     * Проверка блокировки в канале:
     */
    public static function hasVoice($id, $ip)
    {
		$cache = KVS::getInstance();
		$devoice_list = unserialize($cache -> get(__CLASS__, $id, 'devoiced'));
		return !in_array($ip, $devoice_list);
    }

    /**
     * Блокировка пользователя:
     */
    public static function deVoice($id, $message_id)
    {
		$cache = KVS::getInstance();
        if (($ip = $cache -> get(__CLASS__, $id, $message_id .':ip')) !== false) {
            $devoice_list = unserialize($cache -> get(__CLASS__, $id, 'devoiced'));
            $key = md5(self::$SALT . $ip);

            if (!is_array($devoice_list))      $devoice_list = array();
            if (!in_array($ip, $devoice_list)) $devoice_list[md5(self::$SALT . $ip)] = $ip;

            $cache -> set(__CLASS__, $id, 'devoiced', serialize($devoice_list));
            return $key;
        }
        return false;
    }

    /**
     * Разблокировка пользователя:
     */
    public static function enVoice($id, $key)
    {
		$cache = KVS::getInstance();
		$devoice_list = unserialize($cache -> get(__CLASS__, $id, 'devoiced'));
		unset($devoice_list[$key]);
        $cache -> set(__CLASS__, $id, 'devoiced', serialize($devoice_list));
		return true;
    }

    /**
     * Очистка войс листа:
     */
    public static function clearVoice($id)
    {
		$cache = KVS::getInstance();
		$cache -> remove(__CLASS__, $id, 'devoiced');
		return true;
    }

    /**
     * Добавление слова в блоклист:
     */
     public static function blockWord($id, $blocked_word)
     {
         $kvs = KVS::getInstance();
         if ($kvs -> exists(__CLASS__, $id, 'blockwords'))
		$blockwords = unserialize($kvs -> get(__CLASS__, $id, 'blockwords'));
	 else
		$blockwords = array();

	$blockwords[] = $blocked_word;

	$kvs -> set(__CLASS__, $id, 'blockwords', serialize($blockwords));
	return true;
     }

    /**
     * Получение слов в блеклисте:
     */
    public static function getBlockList($id)
    {
	$kvs = KVS::getInstance();
         if ($kvs -> exists(__CLASS__, $id, 'blockwords'))
		return unserialize($kvs -> get(__CLASS__, $id, 'blockwords'));
	return array();
    }

    /**
     * Удаление слова из блоклиста:
     */
     public static function unblockWord($id, $blocked_word_id)
     {
         $kvs = KVS::getInstance();
         if ($kvs -> exists(__CLASS__, $id, 'blockwords'))
		$blockwords = unserialize($kvs -> get(__CLASS__, $id, 'blockwords'));
	 else
		return false;

	array_splice($blockwords, $blocked_word_id, 1);
	$kvs -> set(__CLASS__, $id, 'blockwords', serialize($blockwords));
	return true;
     }

    /**
     * Проверка пароля (и получение id канала):
     */
    public static function TestChatChannel($id, $password = false)
    {
		$cache = KVS::getInstance();
        if (($room = unserialize($cache -> get('Chat_ChatRoomsModel', $id, 'room'))) != false)
        {
            if ($room['password'] && $password !== false)
            {
                if ($room['password'] == $password)
                    return self::GetChatChannel($room['room_id'], $password);

                return false;
            }
            elseif ($room['password'])
                return false;
            else
                return $room['room_id'];
        }
        return null;
    }

    /**
     * Получение id канала:
     */
    public static function GetChatChannel($id, $password = false)
    {
        if ($password == false)
            return $id;

        return md5(self::$SALT . $id . $password);
    }

    /**
     * Отправка сообщения в канал:
     */
    public static function AddMessage($id, $message, $password = false)
    {
		$cache = KVS::getInstance();
		$session = Session::getInstance();
        $channel = self::GetChatChannel($id, $password);

        $message_id = $cache -> incr(__CLASS__, $id, 'nextId');
        $message    = TexyHelper::markup($message, !$session -> isAdminSession(), false);
        $date       = time();

        $cache -> set(__CLASS__, $id, $message_id .':ip', $_SERVER['REMOTE_ADDR']);
        $cache -> expire(__CLASS__, $id, $message_id .':ip', 60 * 5);

        EventModel::getInstance()
            -> ClientBroadcast('chat_'. $channel, 'message', array('type' => 'normal', 'id' => $message_id, 'message' => $message, 'date' => date('H:i:s', $date)));

        /**
         * Отправка сообщения администратору:
         */

        self::LogMessage($channel, $message, $message_id, $date);

        return $message_id;
    }

    /**
     * Отправка сообщения ошибки в канал:
     */
    public static function AddErrorMessage($id, $message, $password = false)
    {
        $channel = self::GetChatChannel($id, $password);

        EventModel::getInstance()
            -> ClientBroadcast('chat_'. $channel, 'message', array('type' => 'error', 'message' => $message));

        return true;
    }

    /**
     * Отправка информационного сообщения в канал:
     */
    public static function AddInfoMessage($id, $message, $password = false)
    {
        $channel = self::GetChatChannel($id, $password);

        EventModel::getInstance()
            -> ClientBroadcast('chat_'. $channel, 'message', array('type' => 'info', 'message' => $message));

        return true;
    }

    /**
     * Логирование сообщения:
     */
    public static function LogMessage($channel, $message, $message_id, $date)
    {
        $cache = KVS::getInstance();
        $log_text = '

							№'.$message_id.': '.date('H:i:s d.m.Y', $date).'

									'.$message.'

';

		$cache -> listAdd(__CLASS__, $channel, 'log', $log_text);
		$cache -> listTrim(__CLASS__, $channel, 'log', 0, 1000);
    }

    /**
     * Получение лога:
     */
    public static function GetLog($channel)
    {
        $cache = KVS::getInstance();
        $log = $cache -> listGet(__CLASS__, $channel, 'log');
        return implode("\n", $log);
    }
}


code:
<?php
/**
 * Модель комнат чата:
 */
class Chat_ChatRoomsModel
{
	/**
	 * Создание комнаты:
	 */
	public static function CreateRoom($params)
	{
		$cache = KVS::getInstance();

		$params['title']       = TexyHelper::typo($params['title']);
		$params['description'] = TexyHelper::typo($params['description']);

		$id = uniqid(md5(time()));

		$record = array(
			'room_id'     => $id,
			'alias'       => null,
			'title'       => $params['title'],
			'description' => $params['description'],
			'controlword' => $params['controlword'],
			'password'    => $params['password'],
			'public'      => $params['public']
		);

		$cache -> set(__CLASS__, $id, 'room', serialize($record));
		$cache -> set(__CLASS__, $id, 'update', time());
		$cache -> listAdd(__CLASS__, 'rooms', null, $id);

		EventModel::getInstance()
			-> Broadcast('add_room', $record);

		return $id;
	}

	/**
	 * Создание общей комнаты:
	 */
	public static function CreateCommonRoom()
	{
		$cache = KVS::getInstance();

		$id = substr(md5('common') . md5('common'), 0, 45);

		$record = array(
		    'room_id'     => $id,
            'title'       => 'Общая комната',
            'description' => 'Свободное общение',
            'password'    => '',
            'controlword' => COMMON_ROOM_CONTROLWORD,
            'public'      => true
        );

		$cache -> set(__CLASS__, $id, 'room', serialize($record));
		$cache -> set(__CLASS__, $id, 'update', time());
	}

	/**
	 * Получение избранных и остальных комнат:
	 */
	public static function GetRooms()
	{
		$cache = KVS::getInstance();
		$rooms = $cache -> listGet(__CLASS__, 'rooms', null);

		$session = Session::getInstance();
		$favorites = $session -> persistenceGet('chats_favorites', array());

		$result = array(
			'favorites' => array(),
			'public'    => array()
		);

		foreach($favorites as $id)
		{
			$data = unserialize($cache -> get(__CLASS__, $id, 'room'));
			if ($data)
				$result['favorites'][$id] = $data;
			else
				unset($favorites[$id]);
		}

		foreach($rooms as $id)
		{
			$update = $cache -> get(__CLASS__, $id, 'update');

			// Если время обновления слишком старое, удаляем:
			if ((time() - $update) > 60 * 60 * 48) {
				self::EditRoom($id, array('public' => false));
				continue;
			}

			// Если комната уже в списке избранного, пропускаем:
			if (in_array($id, $favorites)) continue;

			$data   = unserialize($cache -> get(__CLASS__, $id, 'room'));
			if ($data) {
				if ($data['public'])
					$result['public'][$id] = $data;
			} else
				$cache -> listRemove(__CLASS__, 'rooms', null, $id);
		}

		$session -> persistenceSet('chats_favorites', $favorites);
		shuffle($result['public']);

		return $result;
	}

	/**
	 * Получение всех комнат:
	 */
	public static function GetAllRooms()
	{
		$cache = KVS::getInstance();
		$rooms = $cache -> listGet(__CLASS__, 'rooms', null);

		$rooms_data = array();
		foreach($rooms as $id)
		{
			$data = unserialize($cache -> get(__CLASS__, $id, 'room'));
			if ($data)
				$rooms_data[$id] = $data;
			else
				$cache -> listRemove(__CLASS__, 'rooms', null, $id);
		}

		return $rooms_data;
	}

	/**
	 * Получение комнаты по ID:
	 */
	public static function GetRoom($id)
	{
		$cache = KVS::getInstance();
		$data = unserialize($cache -> get(__CLASS__, $id, 'room'));
		return $data;
	}

	/**
	 * Получение комнаты по Alias:
	 */
	public static function GetRoomByAlias($alias)
	{
		$cache = KVS::getInstance();
		$id    = $cache -> get(__CLASS__, 'alias', $alias);

		if ($id)
			return unserialize($cache -> get(__CLASS__, $id, 'room'));

		return false;
	}

	public static function SetAlias($id, $newAlias) {
		$cache = KVS::getInstance();
		if (!$cache -> exists(__CLASS__, 'alias', $newAlias))
		{
			$cache -> set(__CLASS__, 'alias', $newAlias, $id);
			return true;
		}
		return false;
	}

	public static function RemoveAlias($alias)
	{
		$cache = KVS::getInstance();
		$cache -> remove(__CLASS__, 'alias', $alias);
	}

	/**
	 * Редактирование комнаты:
	 */
	public static function EditRoom($id, $params)
	{
		$cache = KVS::getInstance();
		$room = self::GetRoom($id);
		if ($room) {
			$params = array_merge($room, $params);

			$params['title']       = TexyHelper::typo($params['title']);
			$params['description'] = TexyHelper::typo($params['description']);

			$record = array(
				'room_id'     => $id,
				'alias'       => $params['alias'],
				'title'       => $params['title'],
				'description' => $params['description'],
				'controlword' => $params['controlword'],
				'password'    => $params['password'],
				'public'      => $params['public']
			);

			$cache -> set(__CLASS__, $id, 'room', serialize($record));

			EventModel::getInstance()
				-> Broadcast('edit_room', $record);
		}
	}

	/**
	 * Обновление статуса обновления комнаты:
	 */
	public static function TouchRoom($id)
	{
		$cache = KVS::getInstance();
		$cache -> set(__CLASS__, $id, 'update', time());
	}

	/**
	 * Удаление комнаты:
	 */
	public static function RemoveRoom($id)
	{
		$cache = KVS::getInstance();
		$room  = self::GetRoom($id);

		$cache -> listRemove(__CLASS__, 'rooms', null, $id);
		$cache -> remove(__CLASS__, $id, 'room');
		$cache -> remove(__CLASS__, $id, 'info');
		$cache -> remove('Chat_ChatModel', Chat_ChatModel::GetChatChannel($id, $room['password']), 'log');

		EventModel::getInstance()
			-> Broadcast('remove_room', $id);
	}

	/**
	 * Обновление избранного:
	 */
	public static function UpdateFavorites($favorites)
	{
		$session = Session::getInstance();
		$session -> persistenceSet('chats_favorites', $favorites);
	}

	/**
	 * Получение количества участников в комнате:
	 */
	public static function GetRoomOnline($id)
	{
		$cache = KVS::getInstance();
		$stats = unserialize($cache -> get(__CLASS__, $id, 'stats'));

        foreach($stats['visitors'] as $ip => $time)
		{
		    if ((time() - $time) > 60 * 4)
			{
				$stats['online']--;
			}
		}

		return $stats['online'];
	}

	/**
	 * Установка количества участников в комнате:
	 */
	public static function SetRoomOnline($id)
	{
		$ip    = $_SERVER['REMOTE_ADDR'];
		$cache = KVS::getInstance();

		if ($cache -> exists(__CLASS__, $id, 'stats'))
			$stats = unserialize($cache -> get(__CLASS__, $id, 'stats'));
		else
			$stats = array('online' => 0, 'visitors' => array());

	    $online_cache = $stats['online'];

		foreach($stats['visitors'] as $is => $time)
		{
			if ((time() - $time) > 60 * 4)
			{
				unset($stats['visitors'][$is]);
				$stats['online']--;
			}
		}

		if (!array_key_exists($ip, $stats['visitors']))
		{
			$stats['visitors'][$ip] = time();
			$stats['online']++;
		}

		$cache -> set(__CLASS__, $id, 'stats', serialize($stats));

        if ($stats['online'] != $online_cache)
		    EventModel::getInstance()
			    -> Broadcast('room_stats_updated', array('room_id' => $id, 'stats' => $stats));

		return true;
	}

    /**
     * Установка "инфорации" канала:
     */
    public static function SetInfo($id, $message)
    {
		$cache   = KVS::getInstance();
        $message = TexyHelper::markup($message, true, false);

        $cache -> set(__CLASS__, $id, 'info', $message);

        return $message;
    }

    /**
     * Получение "информации" канала:
     */
    public static function GetInfo($id)
    {
		$cache   = KVS::getInstance();
		return $cache -> get(__CLASS__, $id, 'info');
    }
}

/**
 * Обработчики событий:
 */
EventModel::getInstance()
	/**
	 * Добавление комнаты:
	 */
	-> AddEventListener('add_room', function($data) {
	    if ($data['public'] == true)
	        EventModel::getInstance()
			    -> ClientBroadcast(
				    'chats', 'add_room'
			    );
	})
	-> AddEventListener('edit_room', function($data) {
	    EventModel::getInstance()
			-> ClientBroadcast(
		       'chat_'. $data['room_id'], 'edit_room', array(
		            'room_id'     => $data['room_id'],
		            'title'       => $data['title'],
		            'description' => $data['description'],
		            'public'      => $data['public'],
		            'password'    => (bool)$data['password']
		        )
	        )
			-> ClientBroadcast(
		       'chats', 'edit_room', array(
		            'room_id'     => $data['room_id'],
		            'title'       => $data['title'],
		            'description' => $data['description'],
		            'public'      => $data['public'],
		            'password'    => (bool)$data['password']
		        )
	        );
	})
	-> AddEventListener('room_stats_updated', function($data) {
	    EventModel::getInstance()
			-> ClientBroadcast(
		       'chat_'. $data['room_id'], 'stats_updated', array(
		            'room_id' => $data['room_id'],
		            'online'  => $data['stats']['online']
		        )
	        )
			-> ClientBroadcast(
		       'chats', 'stats_updated_room', array(
		            'room_id' => $data['room_id'],
		            'online'  => $data['stats']['online']
		        )
	        );
	});


code:
<?php
/**
 * Модель контроля и общих настроек:
 */
class ControlModel
{
	/**
	 * Глобальное сообщение сайта:
	 */
	public static function isGlobalMessage()
	{
		$settings = self::GetSettings();
		return @$settings['global_message'];
	}

	/**
	 * Прeмодерация сообщения:
	 */
	public static function isPostPremoderation()
	{
		$settings = self::GetSettings();
		return @$settings['premoderation'];
	}

	/**
	 * Ручное одобрение поста:
	 */
	public static function isPostHandApproving()
	{
		$settings = self::GetSettings();
		return @$settings['handapprove'];
	}

	/**
	 * Фильтр флуда:
	 */
	public static function isFloodFilter()
	{
		$settings = self::GetSettings();
		return @$settings['flood_filter'];
	}

	/**
	 * Фильтр одинаковых постов:
	 */
	public static function isSamepostFilter()
	{
		$settings = self::GetSettings();
		return @$settings['samepost_filter'];
	}

	/**
	 * Фильтр одинаковых комментариев:
	 */
	public static function isSamecommFilter()
	{
		$settings = self::GetSettings();
		return @$settings['samecomment_filter'];
	}

	/**
	 * Капча на постинг сообщения:
	 */
	public static function isPostCaptcha()
	{
		$settings = self::GetSettings();
		if(@$settings['post_captcha'])
			return true;

		$session  = Session::getInstance();

		if (@$_SESSION['open_from'] != $_SERVER['REMOTE_ADDR']) {
			$session -> persistenceSet('captcha_mode', true);
			$_SESSION['open_from'] = $_SERVER['REMOTE_ADDR'];
		}

		if ($session -> persistenceGet('captcha_mode', true))
			return true;

		return false;
	}

	/**
	 * Капча на постинг комментария:
	 */
	public static function isCommentCaptcha()
	{
		$kvs = KVS::getInstance();
		$settings = self::GetSettings();
		if(@$settings['post_comment_captcha'])
			return true;

		if ($kvs -> exists('ControlModel', 'timeblock', $_SERVER['REMOTE_ADDR']))
			return true;

		$session  = Session::getInstance();

		if (@$_SESSION['open_from'] != $_SERVER['REMOTE_ADDR']) {
			$session -> persistenceSet('captcha_mode', true);
			$_SESSION['open_from'] = $_SERVER['REMOTE_ADDR'];
		}

		if ($session -> persistenceGet('captcha_mode', true))
			return true;

		return false;

	}

	/**
	 * Капча на оценку новости:
	 */
	public static function isPostRateCaptcha()
	{
		$settings = self::GetSettings();
		if(@$settings['post_rate_captcha'])
			return true;

		$session  = Session::getInstance();

		if (@$_SESSION['open_from'] != $_SERVER['REMOTE_ADDR']) {
			$session -> persistenceSet('captcha_mode', true);
			$_SESSION['open_from'] = $_SERVER['REMOTE_ADDR'];
		}

		if ($session -> persistenceGet('captcha_mode', true))
			return true;

		return false;
	}

	/**
	 * Капча на добавление ссылки в ротатор:
	 */
	public static function isLiveCaptcha()
	{
		$settings = self::GetSettings();
		$kvs = KVS::getInstance();

		if(@$settings['live_captcha'])
			return true;

		if ($kvs -> exists('ControlModel', 'timeblock', $_SERVER['REMOTE_ADDR']))
			return true;

		$session  = Session::getInstance();

		if (@$_SESSION['open_from'] != $_SERVER['REMOTE_ADDR']) {
			$session -> persistenceSet('captcha_mode', true);
			$_SESSION['open_from'] = $_SERVER['REMOTE_ADDR'];
		}

		if ($session -> persistenceGet('captcha_mode', true))
			return true;

		return false;
	}

	/**
	 * Получение интервала постинга:
	 */
	public static function getPostInterval()
	{
		$kvs = KVS::getInstance();
		if ($kvs -> exists('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']))
			return $kvs -> lifetime('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']);

		$time      = time();
		$session   = Session::getInstance();
		$settings  = self::GetSettings();
		$last_post = $session -> persistenceGet('last_post_date', time());
		$interval = $time - $last_post;

		if ($interval > 0 && $interval < $settings['post_interval'])
			return $settings['post_interval'] - $interval;

		return 0;
	}

	/**
	 * Получение интервала комментариев:
	 */
	public static function getPostCommentInterval()
	{
		$session  = Session::getInstance();
		$settings = self::GetSettings();

		$kvs = KVS::getInstance();

		if ($kvs -> exists('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']))
			return $kvs -> lifetime('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']);

		$last_post = $session -> persistenceGet('last_comment_date', time());
		$interval = time() - $last_post;

		if ($interval > 0 && $interval < $settings['post_comment_interval'])
			return $settings['post_comment_interval'] - $interval;

		return 0;
	}

	/**
	 * Получение интервала комментариев:
	 */
	public static function getBoardPostInterval()
	{
		$session  = Session::getInstance();

		$kvs = KVS::getInstance();
		if ($kvs -> exists('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']))
			return $kvs -> lifetime('ControlModel', 'timeban', $_SERVER['REMOTE_ADDR']);

		$last_post = $session -> persistenceGet('last_board_post_date', time());
		$interval = time() - $last_post;

		if ($interval > 0 && $interval < 7)
			return 7 - $interval;

		return 0;
	}

	/**
	 * Получение интервала ротатора:
	 */
	public static function getLiveInterval()
	{
		$time     = time();
		$session  = Session::getInstance();
		$settings = self::GetSettings();
		$interval = $time - $session -> persistenceGet('last_live_date', $time);

		if ($interval > 0 && $interval < $settings['live_interval'])
			return $settings['live_interval'] - $interval;

		return 0;
	}

	/**
	 * Получение длины каптчи:
	 */
	public static function getCaptchaLength()
	{
		$settings = self::GetSettings();
		$session  = Session::getInstance();
		$length = $session -> persistenceGet('captcha_mode_length');

		if ($length == false || $length == '' || $length == 0)
			return $settings['captcha_length'];

		return $length;
	}

	/**
	 * Получение количества голосов для одобрения:
	 */
	public static function getRatedCount()
	{
		$settings = self::GetSettings();
		return @$settings['rated_count'];
	}

	/**
	 * Проверка текста на слова из вордфильтра:
	 */
	public static function checkContent($text)
	{
		$settings = self::GetSettings();
		$session  = Session::getInstance();
		$words    = self::GetWordfilter();
		$text_cl  = preg_replace("/[^a-zа-я0-9 .]/iu", "", trim(preg_replace("/(&#?[^ ]+;)/", "", $text)));

		if (is_array($words) && sizeof($words) > 0 && $words[0] != '') {
			if (preg_match('~('. implode('|', $words) .')~iu', $text_cl)) {
				if (@$settings['wordfilter_block'])
					return false;

				$session -> persistenceSet('captcha_mode', true);
				$session -> persistenceSet('captcha_mode_length', 10);
				return true;
			}
		}

		if ($session -> persistenceGet('last_post_text', false)) {
			similar_text($session -> persistenceGet('last_post_text'), $text, $percent);
			if ($percent > 70) {
				$session -> persistenceSet('captcha_mode', true);
				$session -> persistenceSet('captcha_mode_length', @$settings['captcha_length'] + 2);
			}
		}

		return true;
	}

	/**
	 * Проверка контента (борды):
	 */
	public static function checkBoardPost($text)
	{
		$session  = Session::getInstance();
		$text     = trim(htmlspecialchars_decode($text));

		if (empty($text))
			return true;

		if ($session -> persistenceGet('last_board_post_text', false)) {
			similar_text($session -> persistenceGet('last_board_post_text'), $text, $percent);
			if ($percent > 70) {
				$session -> persistenceSet('captcha_mode', true);
				$session -> persistenceSet('captcha_mode_length', @$settings['captcha_length'] + 2);
				return false;
			}
		}

		$session -> persistenceSet('last_board_post_text', $text);

		return true;
	}

	/**
	 * Проверка на спам:
	 */
	public static function checkSpam($text)
	{
		$words = self::GetSpamfilter();
		$text  = html_entity_decode($text);

		if (is_array($words) && sizeof($words) > 0 && $words[0] != '') {
			if (preg_match('~('. implode('|', $words) .')~iu', $text)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Проверка на флуд:
	 */
	public static function checkFlood($text)
	{
		$linefactor = 0.5;
		$minlines = 3;
		$maxlinks = 10;
		$bracketfactor = 0.05;
		$minbrackets = 4;
		$wordfactor = $linefactor;
		$minwords = 10;
		$minletters = 200;
		$wlengthfactor = 25;
		$badlinefactor = 0.25;

		// удаление разметки
		$text = preg_replace('/%/', '', $text);
		$text = preg_replace('/\*/', '', $text);
		$text = preg_replace('/\=/', '', $text);
		$text = preg_replace('/(?<!\S)\-(?!\s)/u', '', $text);
		$text = preg_replace('/`/', '', $text);
		// схожих букв
		$cyr = array('А', 'а', 'В', 'Д', 'Е', 'е', 'К', 'к', 'М', 'м', 'Н', 'н', 'О', 'о', 'Р', 'р', 'С', 'с', 'Т', 'У', 'у', 'Х', 'х', 'Ь', 'ь');
		$lat = array('A', 'a', 'B', 'D', 'E', 'e', 'K', 'k', 'M', 'm', 'H', 'H', 'O', 'o', 'P', 'p', 'C', 'c', 'T', 'Y', 'y', 'X', 'x', 'b', 'b');
		$text = mb_strtolower(strip_tags(str_replace($cyr, $lat, $text)));
		// и знаков препинания (кроме скобок, которые отдельно считаем)
		$text = preg_replace('/((?!\(\))\pP)+/u', '', $text);

		if(!strlen($text)) return true;

		$msglines = array_filter(array_map('rtrim', explode("\n", str_replace("\r", '', $text))));

		// вычисление коэффициента уникальных строк, игнор при недостаточном количестве всех строк
		// проверка деления на ноль для предотвращения 500 ошибки в редких случаях
		if (count($msglines) != 0) {
			if ((count(array_unique($msglines)) / count($msglines) < $linefactor) && (count($msglines) > $minlines)) {
				return false;
			}
		} else {
			return false;
		}

		$badlines = 0;
		foreach($msglines as $msgline) {
			// подсчёт ссылок (http) в одной строке
			if (substr_count($msgline, 'http') > $maxlinks) {
				return false;
				break;
			}

			// отбрасываем скобкодебилов
			if (substr_count($msgline, ')') / strlen($msgline) > $bracketfactor) {
				if (substr_count($msgline, ')') < $minbrackets) {
					if ((substr_count($msgline, ')') != substr_count($msgline, '(')) && (substr_count($msgline, ')') > 1)) {
						return false;
						break;
					}
				} else {
					return false;
					break;
				}
			}
			if ((substr_count($msgline, ')') / strlen($msgline) > $bracketfactor) && (substr_count($msgline, ')') > $minbrackets)) {
				if (substr_count($msgline, '(') < $minbrackets) {
					if ((substr_count($msgline, ')') != substr_count($msgline, '(')) && (substr_count($msgline, ')') > 1)) {
						return false;
						break;
					}
				} else {
					return false;
					break;
				}
			}

			// вычисление коэффициента уникальных слов, подобно коэффициенту строк
			$linewords = explode(" ", $msgline);
			if ((count(array_unique($linewords)) / count($linewords) < $wordfactor) && (count($linewords) > $minwords)) {
				// подсчёт "плохих" строк с недостаточным коэффициентом
				$badlines = ++$badlines;
			}
			// если среднее количество букв на слово слишком крупное для общей длины строки, также считаем её за "плохую"
			if ((strlen($msgline) / count($linewords) > $wlengthfactor) && (strlen($msgline) > $minletters)) {
				$badlines = ++$badlines;
			}
		}
		if ($badlines / count($msglines) > $badlinefactor) {
			return false;
		}

		return true;
	}

	/**
	 * Проверка уникальности поста:
	 */
	public static function checkPostUnique($text)
	{
		$GLOBALS['text_test'] = true;
		$text = TexyHelper::markup($text);
		$GLOBALS['text_test'] = false;

		if (Blog_BlogPostsModel::PostWithTextExists($text)) {
			return false;
		}

		return true;
	}

	/**
	 * Проверка уникальности комментария:
	 */
	public static function checkCommUnique($text)
	{
		$GLOBALS['text_test'] = true;
		$text = TexyHelper::markup($text);
		$GLOBALS['text_test'] = false;

		if (Blog_BlogCommentsModel::CommentWithTextExists($text)) {
			return false;
		}

		return true;
	}

	/**
	 * Получение все настроек:
	 */
	public static function GetSettings()
	{
		static $settings;
		if (!is_array($settings))
		{
			$cache    = KVS::getInstance();
			$settings = unserialize($cache -> get(__CLASS__, null, 'settings'));
		}
		foreach ($settings as $name => &$value) {
			if (preg_match('/(count|length|interval)/', $name)) {
				$value = is_numeric($value) ? $value : 0;
			}
		}
		return $settings;
	}

	/**
	 * Установка настроек:
	 */
	public static function SetSettings($mods = array())
	{
		foreach ($mods as $name => &$value) {
			if (preg_match('/(count|length|interval)/', $name)) {
				$value = is_numeric($value) ? $value : 0;
			}
		}
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'settings', serialize($mods));
	}

	/**
	 * Получение блеклиста:
	 */
	public static function GetWordfilter()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'words'));
	}

	/**
	 * Установка блеклиста:
	 */
	public static function SetWordfilter($words = array())
	{
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'words', serialize($words));
	}

	/**
	 * Получение спам:
	 */
	public static function GetSpamfilter()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'spam'));
	}

	/**
	 * Установка спам:
	 */
	public static function SetSpamfilter($words = array())
	{
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'spam', serialize($words));
	}

	/**
	 * Получение блеклиста ссылок:
	 */
	public static function GetLinkfilter()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'links'));
	}

	/**
	 * Проверка ссылки:
	 */
	public static function CheckLinkfilter($test_link)
	{
		if(isset($test_link) && $test_link) {
			$links = self::GetLinkfilter();

			if($links && !empty($links)) {
				foreach($links as $link)
					if ($link != '' && preg_match('#^'.$link.'#i', urldecode($test_link)))
						return true;
			}
		}
		return false;
	}

	/**
	 * Установка блеклиста ссылок:
	 */
	public static function SetLinkfilter($words = array())
	{
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'links', serialize($words));
	}

	/**
	 * Получение списка модераторов:
	 */
	public static function GetModerators()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'mods'));
	}

	/**
	 * Установка списка модераторов:
	 */
	public static function SetModerators($mods = array())
	{
		$cache = KVS::getInstance();
		return $cache -> set(__CLASS__, null, 'mods', serialize($mods));
	}

	/**
	 * Проверка прав модератора:
	 */
	public static function checkModrights($category_id)
	{
		$session = Session::getInstance();
		if ($session -> isModeratorSession())
		{
			if ($_SESSION['auth']['category'] == '*' || (int)$_SESSION['auth']['category'] == $category_id)
				return true;
		}
		return false;
	}

	/**
	 * Добавление события в лог управления:
	 */
	public static function logModEvent($event)
	{
		$cache = KVS::getInstance();

		$log   = (array)unserialize($cache -> get(__CLASS__, null, 'log'));
		$log[] = $event;

		if (sizeof($log) > 100)
			array_shift($log);

		$cache -> set(__CLASS__, null, 'log', serialize($log));
	}

	/**
	 * Получение лога управления:
	 */
	public static function getLogModEvent()
	{
		$cache = KVS::getInstance();
		return (array)unserialize($cache -> get(__CLASS__, null, 'log'));
	}
}


code:
<?php
/**
 * Модель, отвечающая за запись обработчиков событий и вызова этих обработчиков:
 */
class EventModel
{
	/**
	 * Массив лямбда-функций обработчиков:
	 */
	private $listeners = array();

	/**
	 * Подключение к рилплексору:
	 */
	private $connection;

	/**
	 * Вызов экземпляра класса:
	 */
	public static function getInstance()
	{
		static $instance;

		if (!is_object($instance))
			$instance = new EventModel();

		return $instance;
	}

	/**
	 * Конструктор:
	 */
	private function __construct()
	{
		$this -> connection = new Dklab_Realplexor(REALPLEXOR_HOST, REALPLEXOR_PORT, "1chan_");
	}

	/**
	 * Подпись обработчика на событие:
	 */
	public function AddEventListener($event, $lambda)
	{
		$this -> listeners[$event][] = $lambda;
		return $this;
	}

	/**
	 * Очистка обработчиков события:
	 */
	public function RemoveEventListeners($event)
	{
		unset($this -> listeners[$event]);
	}

	/**
	 * Вызов цепочки обработчиков:
	 */
	public function Broadcast($event, $data = array())
	{
		if (!empty($this -> listeners))
		{
			foreach($this -> listeners as $event_name => $listeners)
			{
				if (fnmatch($event_name, $event))
				{
					foreach($listeners as $listener)
					{
						$listener($data, $event);
					}
				}
			}
		}
		return true;
	}

	/**
	 * Сообщение о событии на клиенты:
	 */
	public function ClientBroadcast($channel, $event, $data = true, $ids = null)
	{
		$this -> connection -> send($channel,
			array(
				'event' => $event,
				'data'  => $data
			),
			$ids
		);
/**
		$redis = new Redis();
		$redis -> publish($channel, json_encode(array('event' => $event, 'data' => $data)));
**/
		return $this;
	}
}


code:
<?php
/**
 * Модель статических страниц:
 */
class StaticModel
{
	/**
	 * Проверка существования страницы:
	 */
	public static function isExists($page)
	{
		$cache = KVS::getInstance();
		$page  = trim($page, '/');

		$pages = (array)unserialize($cache -> get(__CLASS__, null, 'page_list'));
		return array_key_exists($page, $pages);
	}

	/**
	 * Получение списка всех страниц:
	 */
	public static function GetPages()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'page_list'));
	}

	/**
	 * Получение страницы:
	 */
	public static function GetPage($page)
	{
		$cache = KVS::getInstance();
		$page  = trim($page, '/');

		$pages = (array)unserialize($cache -> get(__CLASS__, null, 'page_list'));
		if (array_key_exists($page, $pages))
		{
			return $pages[$page];
		}

		return false;
	}

	/**
	 * Создание (редактирование) страницы:
	 */
	public static function SetPage($page, $title, $content, $published)
	{
		$cache = KVS::getInstance();
		$page  = trim($page, '/');
		$pages = (array)unserialize($cache -> get(__CLASS__, null, 'page_list'));

		if (array_key_exists($page, $pages))
			$name = $pages[$page]['name'];
		else
			$name = uniqid(time());

		if (file_put_contents(VIEWS_DIR .'/static/'. $name .'.php', $content))
		{
			$pages[$page]['title']     = $title;
			$pages[$page]['name']      = $name;
			$pages[$page]['published'] = $published;
		}

		$cache -> set(__CLASS__, null, 'page_list', serialize($pages));
	}

	/**
	 * Удаление страницы:
	 */
	public static function RemovePage($page)
	{
		$cache = KVS::getInstance();
		$page  = trim($page, '/');
		$pages = (array)unserialize($cache -> get(__CLASS__, null, 'page_list'));

		if (array_key_exists($page, $pages))
		{
			@unlink(VIEWS_DIR .'/static/'. $pages[$page]['name'] .'.php');
			unset($pages[$page]);

		}

		$cache -> set(__CLASS__, null, 'page_list', serialize($pages));
		return true;
	}

	/**
	 * Получение списка файлов:
	 */
	public static function GetFiles()
	{
		$cache = KVS::getInstance();
		return unserialize($cache -> get(__CLASS__, null, 'file_list'));
	}

	/**
	 * Добавление файла
	 */
	public static function CreateFile($file)
	{
		if (is_uploaded_file($file['tmp_name']))
		{
			$cache = KVS::getInstance();
			$files = (array)unserialize($cache -> get(__CLASS__, null, 'file_list'));

			move_uploaded_file($file['tmp_name'], WEB_DIR .'/uploads/'. $file['name']);
			array_unshift($files, $file);

			$cache -> set(__CLASS__, null, 'file_list', serialize($files));
		}
	}

	/**
	 * Удаление файла
	 */
	public static function RemoveFile($name)
	{
		$cache = KVS::getInstance();
		$files = (array)unserialize($cache -> get(__CLASS__, null, 'file_list'));

		foreach($files as $k => $file) {
			if ($file['name'] == $name)
				unset($files[$k]);
		}
		@unlink(WEB_DIR .'/uploads/'. $file['name']);

		$cache -> set(__CLASS__, null, 'file_list', serialize($files));
	}
}


code:
Первый канал » Ссылки

                	<form action="/admin/blogLinks" method="post" class="jsNice">

Редактирование ссылок:

						<fieldset>

Имиджборды:<textarea name="imgboards" rows="5"><?php echo($imgboards); ?></textarea>

Другие ссылки:<textarea name="services" rows="5"><?php echo($services); ?></textarea>

						</fieldset>
						<fieldset>
							<input type="submit" value="Отредактировать" />
						</fieldset>
					</form>


code:
Первый канал » Лог действий

Список модераторский действий:

					<fieldset>
						<?php echo($log); ?>
					</fieldset>


code:
Первый канал » Модераторы

                	<form action="/admin/blogModerators" method="post" class="jsNice">

Список модераторов:

						<fieldset>

Имя | Ключ | Класс | Категории:
							<textarea name="moderators" rows="5"><?php echo($moderators); ?></textarea>

						</fieldset>
						<fieldset>
							<input type="submit" value="Обновить список" />
						</fieldset>
					</form>


code:
Первый канал » Настройки

                	<form action="/admin/blogSettings" method="post" class="jsNice">

Настройки:

						<fieldset>

Глобальное сообщение:<input name="global_message" type="text" class="text-long" value="<?php echo(htmlspecialchars($settings['global_message'])); ?>" />

Интервал создания тредов (по-умолчанию):<input name="post_interval" type="number" class="text-small" value="<?php echo($settings['post_interval']); ?>" />

Интервал создания комментариев (по-умолчанию):<input name="post_comment_interval" type="number" class="text-small" value="<?php echo($settings['post_comment_interval']); ?>" />

Интервал ссылок в ротаторе (по-умолчанию):<input name="live_interval" type="number" class="text-small" value="<?php echo($settings['live_interval']); ?>" />

Длина капчи (по-умолчанию):<input name="captcha_length" type="number" class="text-small" value="<?php echo($settings['captcha_length']); ?>" />

Голоса для одобрения:<input name="rated_count" type="number" class="text-small" value="<?php echo($settings['rated_count']); ?>" />

						</fieldset>

Глобальные настройки:

						<fieldset>

<input type="checkbox" name="premoderation" <?php if(@$settings['premoderation']): ?> checked="checked"<?php endif;?> /> Глобальная премодерация

<input type="checkbox" name="handapprove" <?php if(@$settings['handapprove']): ?> checked="checked"<?php endif;?> /> Ручное одобрение постов

<input type="checkbox" name="post_captcha" <?php if(@$settings['post_captcha']): ?> checked="checked"<?php endif;?> /> Глобальная капча на посты

<input type="checkbox" name="post_comment_captcha" <?php if(@$settings['post_comment_captcha']): ?> checked="checked"<?php endif;?> /> Глобальная капча на комментарии

<input type="checkbox" name="post_rate_captcha" <?php if(@$settings['post_rate_captcha']): ?> checked="checked"<?php endif;?> /> Глобальная капча на оценку поста

<input type="checkbox" name="live_captcha" <?php if(@$settings['live_captcha']): ?> checked="checked"<?php endif;?> /> Глобальная капча на добавление ссылки в ротатор

<input type="checkbox" name="wordfilter_block" <?php if(@$settings['wordfilter_block']): ?> checked="checked"<?php endif;?> /> Запрет постинга со словами из вордфильтра

<input type="checkbox" name="flood_filter" <?php if(@$settings['flood_filter']): ?> checked="checked"<?php endif;?> /> Проверка флуда при постинге

<input type="checkbox" name="samepost_filter" <?php if(@$settings['samepost_filter']): ?> checked="checked"<?php endif;?> /> Фильтр одинаковых постов

<input type="checkbox" name="samecomment_filter" <?php if(@$settings['samecomment_filter']): ?> checked="checked"<?php endif;?> /> Фильтр одинаковых комментариев

						</fieldset>
						<fieldset>
							<input type="submit" value="Обновить настройки" />
						</fieldset>
					</form>


code:
Первый канал » Вордфильтр

                	<form action="/admin/blogWordfilter" method="post" class="jsNice">

Запрещенные слова:

						<fieldset>

Регулярные выражения:<textarea name="wordfilter" rows="5"><?php echo($wordfilter); ?></textarea>

						</fieldset>

Запрещенные ссылки:

						<fieldset>

Регулярные выражения:<textarea name="linkfilter" rows="5"><?php echo($linkfilter); ?></textarea>

						</fieldset>

Спам-лист (борды):

						<fieldset>

Регулярные выражения:<textarea name="spamfilter" rows="5"><?php echo($spamfilter); ?></textarea>

						</fieldset>
						<fieldset>
							<input type="submit" value="Обновить список" />
						</fieldset>
					</form>


code:
Первый канал » Добавить комнату чата

						<form action="/admin/chatAdd" method="post" class="jNice">

Основные поля:

							<fieldset>

Заголовок:<input name="title" type="text" class="text-long" />

Пароль управления:<input name="controlword" type="text" class="text-long" value="" />

Пароль на вход:<input name="password" type="text" class="text-long" value="" />

0писание:<textarea name="description" rows="5"></textarea>

							</fieldset>

Дополнительные поля:

							<fieldset>

<input type="checkbox" name="public" checked="checked" /> Публична

							</fieldset>

							<fieldset>
								<input type="submit" value="Добавить комнату" />
							</fieldset>
						</form>


code:
Первый канал » Добавить комнату чата

						<form action="/admin/chatEdit" method="post" class="jNice">
						<input type="hidden" name="id" value="<?php echo($room['room_id']); ?>" />

Основные поля:

							<fieldset>

Заголовок:<input name="title" type="text" class="text-long" value="<?php echo($room['title']); ?>" />

Пароль управления:<input name="controlword" type="text" class="text-long" value="<?php echo($room['controlword']); ?>" />

Пароль на вход:<input name="password" type="text" class="text-long" value="<?php echo($room['password']); ?>" />

0писание:<textarea name="description" rows="5"><?php echo($room['description']); ?></textarea>

							</fieldset>

Дополнительные поля:

							<fieldset>

<input type="checkbox" name="public" <?php if($room['public']):?>checked="checked"<?php endif; ?> /> Публична

							</fieldset>

							<fieldset>
								<input type="submit" value="Сохранить комнату" />
							</fieldset>
						</form>


code:
Первый канал » Список активных чатов

Чаты:

                    	<?php if (empty($chats)): ?>
							Нет чатов для отображения.
                    	<?php else: ?>
                    	<?php $i = 0; ?>
                    	<?php foreach($chats as $room): ?>
							<tr<?php if(++$i % 2): ?> class="odd"<?php endif; ?>>
                                <?php echo $room['title']; ?>
                                <a href="/chat/<?php echo($room['room_id']); ?>/" class="view">View<a href="/admin/chatEdit?id=<?php echo($room['room_id']); ?>" class="edit">Edit<a href="/admin/chatDelete?id=<?php echo($room['room_id']); ?>" class="delete">Delete

                        <?php endforeach; ?>
                        <?php endif; ?>


code:
Общие:

<a href="/admin/chatAdd"<?php if ($this -> getParameter('submenu') == "chat_add"): ?> class="active"<?php endif; ?>>Добавить чат

<a href="/admin/chats"<?php if ($this -> getParameter('submenu') == "chats"): ?> class="active"<?php endif; ?>>Список чатов


code:
<a href="/admin/posts"<?php if ($this -> getParameter('menu') == "posts"): ?> class="active"<?php endif; ?>>ПЕРВЫЙ КАНАЛ

<a href="/admin/chats"<?php if ($this -> getParameter('menu') == "chats"): ?> class="active"<?php endif; ?>>ЧАТЫ

<a href="/admin/staticPages"<?php if ($this -> getParameter('menu') == "static"): ?> class="active"<?php endif; ?>>СТАТИЧЕСКИЕ СТРАНИЦЫ

ВЫХОД


code:
Посты:

<a href="/admin/postAdd"<?php if ($this -> getParameter('submenu') == "post_add"): ?> class="active"<?php endif; ?>>Добавить пост

<a href="/admin/posts"<?php if ($this -> getParameter('submenu') == "post_list"): ?> class="active"<?php endif; ?>>Список постов

<a href="/admin/postSearch"<?php if ($this -> getParameter('submenu') == "post_search"): ?> class="active"<?php endif; ?>>Фильтр постов

<a href="/admin/postComments"<?php if ($this -> getParameter('submenu') == "post_comments"): ?> class="active"<?php endif; ?>>Комментарии

<a href="/admin/postCategory"<?php if ($this -> getParameter('submenu') == "post_category"): ?> class="active"<?php endif; ?>>Категории

Онлайн:

<a href="/admin/onlineChannel"<?php if ($this -> getParameter('submenu') == "online_channel"): ?> class="active"<?php endif; ?>>Каналы

Общее:

<a href="/admin/blogSettings"<?php if ($this -> getParameter('submenu') == "settings"): ?> class="active"<?php endif; ?>>Настройки

<a href="/admin/blogWordfilter"<?php if ($this -> getParameter('submenu') == "wordfilter"): ?> class="active"<?php endif; ?>>Вордфильтр

<a href="/admin/blogModerators"<?php if ($this -> getParameter('submenu') == "moderators"): ?> class="active"<?php endif; ?>>Модераторы

<a href="/admin/blogLinks"<?php if ($this -> getParameter('submenu') == "links"): ?> class="active"<?php endif; ?>>Ссылки

<a href="/admin/blogLog"<?php if ($this -> getParameter('submenu') == "log"): ?> class="active"<?php endif; ?>>Лог управления


code:
<a href="/admin/staticAdd"<?php if ($this -> getParameter('submenu') == "static_add"): ?> class="active"<?php endif; ?>>Добавить страницу

<a href="/admin/staticPages"<?php if ($this -> getParameter('submenu') == "static_pages"): ?> class="active"<?php endif; ?>>Список страниц

<a href="/admin/staticFiles"<?php if ($this -> getParameter('submenu') == "static_files"): ?> class="active"<?php endif; ?>>Управление файлами


code:
Первый канал » Каналы

                	<form action="/admin/onlineChannel" method="post" class="jsNice">

Каналы:

						<fieldset>

Для работы онлайн-ссылок впишите следующее:
							Интернеты :|: http\:\/\/([^\]]+) :|: https://<?php echo TemplateHelper::getSiteUrl(); ?>/
							Интернеты :|: https\:\/\/([^\]]+) :|: https://<?php echo TemplateHelper::getSiteUrl(); ?>/
							<textarea name="channels" rows="5"><?php echo($channels); ?></textarea>

							(мне было лень делать полностью независимые от адреса ссылки, поэтому довольствуйтесь этим костылём)
						</fieldset>
						<fieldset>
							<input type="submit" value="Обновить список" />
						</fieldset>
					</form>